[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## How to contribute to Vanilla Reimplementation\n\n#### **Did you find a bug?**\n\n* Ensure that the associated system is implemented first. E.g. Don't report entities not spawning from spawners, if the\n  spawner logic has not been implemented.\n\n* Open a new GitHub issue if it's not already reported.\n\n* Explain it clearly, with steps (or code) to reproduce it.\n\n#### **Did you write some code that fixes a bug?**\n\n* Open a new GitHub pull-request with the commits if it hasn't already been proposed.\n\n* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.\n\n#### **Do you intend to add a new feature or change an existing one?**\n\n* Do not open a pull-request on GitHub until you have collected positive feedback about the change from a maintainer.\n  You can do this in the [minestom discord](https://discord.gg/pkFRvqB).\n\n#### **Do you have questions about the source code?**\n\n* Ask any question about the code in the associated discord channel.\n\n#### **Do you want to contribute to the Minestom documentation?**\n\n* Feel free to do so! Just make sure to conform to the [standard-readme](https://github.com/RichardLitt/standard-readme)\n  specification when editing the README.md.\n\n## General Contribution Rules\n\n* By contributing to the Vanilla Reimplementation project your code/contribution will be licensed under\n  the [Apache Version 2.0](../LICENSE) license.\n\nMinestom & VRI are community projects. We encourage you to contribute! :)\n\nThanks! :heart: :heart: :heart:\n\n~Minestom Community\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### Java template\n# Compiled class file\n*.class\n*/build/*\nbuild/*\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n# IntelliJ and Gradle files\n.idea/\nout/\n.gradle/\n\n# Minestom\n/minecraft_data/\n/.minestom_tmp/\n\n# Vanilla-like server\nserver.properties\nworld/\n/datapack-tests/mojang-data\n/mojang-data/1.20.4\n/mojang-data/1.21.1\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"vanilla_worldgen_example\"]\n\tpath = vanilla_worldgen_example\n\turl = https://github.com/slicedlime/examples/\n[submodule \"prismarine-minecraft-data\"]\n    path = prismarine-minecraft-data\n    url = https://github.com/PrismarineJS/minecraft-data\n[submodule \"Minestom\"]\n\tpath = Minestom\n\turl = https://github.com/Minestom/Minestom\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# NOT READY FOR PRODUCTION\n\nPriority is currently on the core of Minestom (see below). This project has only a very limited list of features.\nMake sure to check out the project board [here](https://github.com/orgs/Minestom/projects/1).\n\n# About Minestom\n\nSee [Minestom Project on GitHub](https://github.com/Minestom/Minestom)\n\n# Cloning\n\n`git clone --recurse-submodules https://github.com/Minestom/VanillaReimplementation`\n\n# How to use\n\nYou can use this repo by finding the latest release [here](https://jitpack.io/#Minestom/VanillaReimplementation).\nAfter selecting your release, make sure to choose which modules (vanila features) you want.\nThe \"core\" module is required. Everything else is optional and up to you.\n\nOnce you have added your modules to your classpath, you can initiate vri in your server's startup using this snippet:\n`VanillaReimplementation vri = VanillaReimplementation.hook(MinecraftServer.process());`.\n\nSee [here](https://github.com/Minestom/VanillaReimplementation/blob/93f29ab67ffff7d78e34b12ab5f00619109c84c7/server/src/main/java/net/minestom/vanilla/server/VanillaServer.java#L44) for an example.\n\n# How to contribute\n\nSee [the github project](https://github.com/orgs/Minestom/projects/1) for a list of relevant tasks that need to be done.\n"
  },
  {
    "path": "block-update-system/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/BlockUpdateFeature.java",
    "content": "package net.minestom.vanilla;\n\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdateManager;\nimport net.minestom.vanilla.logging.Loading;\nimport net.minestom.vanilla.randomticksystem.RandomTickManager;\nimport org.jetbrains.annotations.NotNull;\n\npublic class BlockUpdateFeature implements VanillaReimplementation.Feature {\n    @Override\n    public void hook(@NotNull HookContext context) {\n        Loading.start(\"Block Update Manager\");\n        BlockUpdateManager.init(context);\n        Loading.finish();\n\n        Loading.start(\"Random Tick Manager\");\n        RandomTickManager.init(context);\n        Loading.finish();\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:blockupdate\");\n    }\n}\n"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdatable.java",
    "content": "package net.minestom.vanilla.blockupdatesystem;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport org.jetbrains.annotations.NotNull;\n\npublic interface BlockUpdatable {\n    /**\n     * Called when a block is updated.\n     *\n     * @param info The block update info.\n     */\n    void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info);\n}\n"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateInfo.java",
    "content": "package net.minestom.vanilla.blockupdatesystem;\n\npublic interface BlockUpdateInfo {\n\n    static DestroyBlock DESTROY_BLOCK() {\n        return new DestroyBlock();\n    }\n\n    static PlaceBlock PLACE_BLOCK() {\n        return new PlaceBlock();\n    }\n\n    static ChunkLoad CHUNK_LOAD() {\n        return new ChunkLoad();\n    }\n\n    static MoveBlock MOVE_BLOCK() {\n        return new MoveBlock();\n    }\n\n    record DestroyBlock() implements BlockUpdateInfo {\n    }\n\n    record PlaceBlock() implements BlockUpdateInfo {\n    }\n\n    record ChunkLoad() implements BlockUpdateInfo {\n    }\n\n    record MoveBlock() implements BlockUpdateInfo {\n    }\n}\n"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateManager.java",
    "content": "package net.minestom.vanilla.blockupdatesystem;\n\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectMap;\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.instance.InstanceChunkLoadEvent;\nimport net.minestom.server.event.instance.InstanceTickEvent;\nimport net.minestom.server.event.player.PlayerBlockBreakEvent;\nimport net.minestom.server.event.player.PlayerBlockPlaceEvent;\nimport net.minestom.server.instance.Chunk;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\n/**\n * A utility class used to facilitate block updates\n */\npublic class BlockUpdateManager {\n    // Block update manager by instance\n    private static final Map<Instance, BlockUpdateManager> instance2BlockUpdateManager =\n            Collections.synchronizedMap(new WeakHashMap<>());\n\n    // Block updatables\n    private static final Short2ObjectMap<BlockUpdatable> blockUpdatables = new Short2ObjectOpenHashMap<>();\n\n    public static void registerUpdatable(short stateId, @NotNull BlockUpdatable updatable) {\n        synchronized (blockUpdatables) {\n            blockUpdatables.put(stateId, updatable);\n        }\n    }\n\n    public static void init(@NotNull VanillaReimplementation.Feature.HookContext context) {\n        EventNode<Event> eventNode = context.vri().process().eventHandler();\n\n        eventNode.addListener(InstanceTickEvent.class, BlockUpdateManager::instanceTick);\n        eventNode.addListener(PlayerBlockBreakEvent.class, event ->\n                BlockUpdateManager.from(event.getPlayer().getInstance())\n                        .scheduleNeighborsUpdate(event.getBlockPosition(),\n                                BlockUpdateInfo.DESTROY_BLOCK())\n        );\n        eventNode.addListener(PlayerBlockPlaceEvent.class, event ->\n                BlockUpdateManager.from(event.getPlayer().getInstance())\n                        .scheduleNeighborsUpdate(event.getBlockPosition(),\n                                BlockUpdateInfo.PLACE_BLOCK())\n        );\n        eventNode.addListener(InstanceChunkLoadEvent.class, event -> {\n            Chunk chunk = event.getChunk();\n            int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE;\n            int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE;\n            int minX = chunk.getChunkX() * Chunk.CHUNK_SIZE_X;\n            int minZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z;\n\n            Instance instance = event.getInstance();\n            BlockUpdateManager.from(instance);\n\n            synchronized (blockUpdatables) {\n                for (int x = minX; x < minX + Chunk.CHUNK_SIZE_X; x++) {\n                    for (int z = minZ; z < minZ + Chunk.CHUNK_SIZE_Z; z++) {\n                        for (int y = minY; y < maxY; y++) {\n                            Block block = chunk.getBlock(x, y, z);\n                            BlockUpdatable updatable = blockUpdatables.get((short) block.stateId());\n                            if (updatable == null) continue;\n                            updatable.blockUpdate(instance, new Vec(x, y, z), BlockUpdateInfo.CHUNK_LOAD());\n                        }\n                    }\n                }\n            }\n        });\n    }\n\n    private static void instanceTick(InstanceTickEvent event) {\n        Instance instance = event.getInstance();\n        from(instance).tick(event.getDuration());\n    }\n\n    public static @NotNull BlockUpdateManager from(@NotNull Instance instance) {\n        return instance2BlockUpdateManager.computeIfAbsent(instance, BlockUpdateManager::new);\n    }\n\n    private final Map<Point, BlockUpdateInfo> updateNeighbors = Collections.synchronizedMap(new LinkedHashMap<>());\n    private final BlockUpdateManager.UpdateHandler updateHandler;\n\n    public BlockUpdateManager(@NotNull BlockUpdateManager.UpdateHandler updateHandler) {\n        this.updateHandler = updateHandler;\n    }\n\n    private BlockUpdateManager(@NotNull Instance instance) {\n        this.updateHandler = (pos, info) -> {\n            if (instance.getBlock(pos).handler() instanceof BlockUpdatable updatable) {\n                updatable.blockUpdate(instance, pos, info);\n            }\n        };\n    }\n\n    public interface UpdateHandler {\n        void update(@NotNull Point pos, @NotNull BlockUpdateInfo info);\n    }\n\n    // Public api methods\n\n    /**\n     * Schedules this position's neighbors to be updated next tick.\n     */\n    public void scheduleNeighborsUpdate(Point pos, BlockUpdateInfo info) {\n        updateNeighbors.put(pos, info);\n    }\n\n    // Public api methods end\n\n    private void tick(int duration) {\n        updateNeighbors(duration);\n    }\n\n    private void updateNeighbors(int duration) {\n        if (updateNeighbors.isEmpty()) {\n            return;\n        }\n\n        // Update all the neighbors\n        for (Map.Entry<Point, BlockUpdateInfo> entry : updateNeighbors.entrySet()) {\n            Point pos = entry.getKey();\n            BlockUpdateInfo info = entry.getValue();\n\n            int x = pos.blockX();\n            int y = pos.blockY();\n            int z = pos.blockZ();\n\n            // For each surrounding block\n            for (int offsetX = -1; offsetX < 2; offsetX++) {\n                for (int offsetY = -1; offsetY < 2; offsetY++) {\n                    for (int offsetZ = -1; offsetZ < 2; offsetZ++) {\n\n                        // If block is not the original block\n                        if (offsetX == 0 && offsetY == 0 && offsetZ == 0) {\n                            continue;\n                        }\n\n                        // Get the block handler at the position\n                        Point blockPos = new Pos(x + offsetX, y + offsetY, z + offsetZ);\n                        updateHandler.update(blockPos, info);\n                    }\n                }\n            }\n        }\n\n        updateNeighbors.clear();\n    }\n}\n"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickManager.java",
    "content": "package net.minestom.vanilla.randomticksystem;\n\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectMap;\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.event.instance.InstanceTickEvent;\nimport net.minestom.server.instance.Chunk;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.WeakHashMap;\n\npublic class RandomTickManager {\n\n    private static final @NotNull String RANDOM_TICK_SYSTEM_PROPERTY = \"vri.gamerule.randomtickspeed\";\n\n    private static final Map<VanillaReimplementation, RandomTickManager> vri2managers =\n            Collections.synchronizedMap(new WeakHashMap<>());\n    private static final Short2ObjectMap<RandomTickable> randomTickables = new Short2ObjectOpenHashMap<>();\n\n    private final VanillaReimplementation vri;\n    private RandomTickManager(VanillaReimplementation vri) {\n        this.vri = vri;\n    }\n\n    public static @NotNull RandomTickManager create(@NotNull VanillaReimplementation vri) {\n        return vri2managers.computeIfAbsent(vri, RandomTickManager::new);\n    }\n\n    public static void init(VanillaReimplementation.Feature.@NotNull HookContext context) {\n        RandomTickManager manager = create(context.vri());\n        context.vri().process().eventHandler().addListener(InstanceTickEvent.class, event -> {\n            int randomTickCount = Integer.parseInt(System.getProperty(RANDOM_TICK_SYSTEM_PROPERTY, \"3\"));\n            manager.handleInstanceTick(event, randomTickCount);\n        });\n    }\n\n    public static void registerRandomTickable(short stateId, RandomTickable randomTickable) {\n        synchronized (randomTickables) {\n            randomTickables.put(stateId, randomTickable);\n        }\n    }\n\n    private void handleInstanceTick(InstanceTickEvent event, int randomTickCount) {\n        Instance instance = event.getInstance();\n        Random instanceRandom = vri.random(instance);\n        synchronized (randomTickables) {\n            for (Chunk chunk : instance.getChunks()) {\n                int minSection = chunk.getMinSection();\n                int maxSection = chunk.getMaxSection();\n                for (int section = minSection; section < maxSection; section++) {\n                    for (int i = 0; i < randomTickCount; i++) {\n                        randomTickSection(instanceRandom, instance, chunk, section);\n                    }\n                }\n            }\n        }\n    }\n\n    private void randomTickSection(Random random, Instance instance, Chunk chunk, int minSection) {\n        int minX = chunk.getChunkX() * Chunk.CHUNK_SIZE_X;\n        int minZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z;\n        int minY = minSection * Chunk.CHUNK_SECTION_SIZE;\n\n        int x = minX + random.nextInt(Chunk.CHUNK_SIZE_X);\n        int z = minZ + random.nextInt(Chunk.CHUNK_SIZE_Z);\n        int y = minY + random.nextInt(Chunk.CHUNK_SECTION_SIZE);\n        Point pos = new Vec(x, y, z);\n\n        Block block = instance.getBlock(x, y, z);\n        RandomTickable randomTickable = randomTickables.get((short) block.stateId());\n        if (randomTickable == null) return;\n        randomTickable.randomTick(new RandomTick(instance, pos, block));\n    }\n\n    private record RandomTick(Instance instance, Point position, Block block) implements RandomTickable.RandomTick {}\n}\n"
  },
  {
    "path": "block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickable.java",
    "content": "package net.minestom.vanilla.randomticksystem;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport org.jetbrains.annotations.NotNull;\n\npublic interface RandomTickable {\n\n    void randomTick(@NotNull RandomTick randomTick);\n\n    interface RandomTick {\n        @NotNull Instance instance();\n        @NotNull Point position();\n        @NotNull Block block();\n    }\n}\n"
  },
  {
    "path": "blocks/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":block-update-system\"))\n    compileOnly(project(\":entity-meta\"))\n    compileOnly(project(\":datapack-loading\"))\n}"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.BlockHandler;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\n/**\n * Represents a singular vanilla block's logic. e.g. white bed, cake, furnace, etc.\n */\npublic abstract class VanillaBlockBehaviour implements BlockHandler {\n\n    protected final @NotNull VanillaBlocks.BlockContext context;\n    protected final short baseBlock;\n    protected final @NotNull Key key;\n\n    protected VanillaBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        this.context = context;\n        this.baseBlock = context.stateId();\n        this.key = Objects.requireNonNull(Block.fromStateId(context.stateId())).key();\n    }\n\n    /**\n     * DO NOT USE THIS.\n     * @see #onPlace(VanillaPlacement) instead.\n     */\n    @Override\n    @Deprecated\n    public void onPlace(@NotNull BlockHandler.Placement placement) {\n    }\n\n    public void onPlace(@NotNull VanillaPlacement placement) {\n    }\n\n    @Override\n    public @NotNull Key getKey() {\n        return key;\n    }\n\n    public interface VanillaPlacement {\n\n        /**\n         * @return the block that will be placed\n         */\n        @NotNull Block blockToPlace();\n\n        /**\n         * @return the instance that will be modified\n         */\n        @NotNull Instance instance();\n\n        /**\n         * @return the position of the block that will be placed\n         */\n        @NotNull Point position();\n\n        /**\n         * Overrides the current block to be placed.\n         *\n         * @param newBlock the new block to be placed\n         */\n        void blockToPlace(@NotNull Block newBlock);\n\n        interface HasPlayer {\n            @NotNull Player player();\n        }\n    }\n\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockLoot.java",
    "content": "package net.minestom.vanilla.blocks;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.player.PlayerBlockBreakEvent;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.loot.LootTable;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.datapack.loot.function.LootFunction;\nimport net.minestom.vanilla.datapack.loot.function.Predicate;\nimport net.minestom.vanilla.files.FileSystem;\nimport net.minestom.vanilla.logging.Logger;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Random;\nimport java.util.function.Consumer;\nimport java.util.random.RandomGenerator;\n\npublic record VanillaBlockLoot(VanillaReimplementation vri, Datapack datapack) {\n\n    private record LootEntry(@Nullable List<LootFunction> functions, List<ItemStack> items, double weight) {\n    }\n\n    public void spawnLoot(@NotNull PlayerBlockBreakEvent event) {\n        String blockName = event.getBlock().key().value();\n        datapack.namespacedData().forEach((namespace, data) -> {\n            FileSystem<LootTable> blocks = data.loot_tables().folder(\"blocks\");\n            var lootTable = blocks.file(blockName + \".json\");\n            if (lootTable == null) return;\n\n            Block blockState = event.getBlock();\n            Point origin = event.getBlockPosition();\n            ItemStack tool = event.getPlayer().getItemInMainHand();\n            Player entity = event.getPlayer();\n            Block blockEntity = blockState.registry().blockEntity() == null ? null : blockState;\n            Random random = vri.random(entity);\n\n            LootContext context = new LootContext.Block(blockState, origin, tool, entity, blockEntity, null);\n\n            List<ItemStack> items = new ArrayList<>();\n            generateLootItems(lootTable, context, random, items::add);\n\n            for (ItemStack item : items) {\n                ItemEntity itemEntity = new ItemEntity(item);\n                itemEntity.setInstance(entity.getInstance(), origin.add(0.5));\n            }\n        });\n    }\n\n    public List<ItemStack> getLoot(LootTable lootTable, LootContext context) {\n        return getLoot(lootTable, context, vri.random(0));\n    }\n\n    public List<ItemStack> getLoot(LootTable lootTable, LootContext context, Random random) {\n        List<ItemStack> items = new ArrayList<>();\n        generateLootItems(lootTable, context, random, items::add);\n        return items;\n    }\n\n    private void generateLootItems(LootTable lootTable, LootContext context, Random random, Consumer<ItemStack> out) {\n        if (lootTable.pools() == null) return; // TODO: handle random_sequence\n        for (LootTable.Pool pool : lootTable.pools()) {\n\n            // Ensure all conditions are met\n            if (fails(pool.conditions(), context)) continue;\n\n            int rolls = pool.rolls().asInt().apply(() -> random);\n\n            // collect all of the loot entries\n            List<LootEntry> entries = new ArrayList<>();\n            for (LootTable.Pool.Entry entry : pool.entries()) {\n\n                // Ensure all conditions are met\n                if (fails(entry.conditions(), context)) continue;\n\n                // now we can add the entries\n                addEntries(context, entry, itemGenerator -> {\n                    double weight = itemGenerator.weight() == null ? 1 : Objects.requireNonNull(itemGenerator.weight()).asDouble().apply(() -> random);\n                    var lootEntries = itemGenerator.apply(datapack, context);\n                    for (List<ItemStack> lootEntryItems : lootEntries) {\n                        entries.add(new LootEntry(itemGenerator.functions(), lootEntryItems, weight));\n                    }\n                });\n            }\n\n            // if there is no entries, we can skip this pool\n            if (entries.isEmpty()) continue;\n\n            // now that we have all the entries, we can roll for them\n            double totalWeight = entries.stream().mapToDouble(LootEntry::weight).sum();\n            for (int i = 0; i < rolls; i++) {\n                LootEntry chosenLootEntry = null;\n                double roll = random.nextDouble() * totalWeight;\n                for (LootEntry lootEntry : entries) {\n                    roll -= lootEntry.weight();\n                    if (roll <= 0) {\n                        chosenLootEntry = lootEntry;\n                        break;\n                    }\n                }\n                Objects.requireNonNull(chosenLootEntry);\n\n                // we now have the loot entry, we need to apply the loot functions\n                LootEntry finalChosenLootEntry = chosenLootEntry;\n                chosenLootEntry.items()\n                        .stream()\n                        // loot entry functions\n                        .map(item -> {\n                            if (finalChosenLootEntry.functions() == null) return item;\n                            for (LootFunction function : finalChosenLootEntry.functions()) {\n                                item = function.apply(new LootFunctionContext(random, item, context));\n                            }\n                            return item;\n                        })\n                        // pool functions\n                        .map(item -> {\n                            if (pool.functions() == null) return item;\n                            for (LootFunction function : pool.functions()) {\n                                item = function.apply(new LootFunctionContext(random, item, context));\n                            }\n                            return item;\n                        })\n                        // table functions\n                        .map(item -> {\n                            if (lootTable.functions() == null) return item;\n                            for (LootFunction function : lootTable.functions()) {\n                                item = function.apply(new LootFunctionContext(random, item, context));\n                            }\n                            return item;\n                        })\n                        // add all of the items to the list\n                        .forEach(out);\n            }\n        }\n    }\n\n    private record LootFunctionContext(RandomGenerator random, ItemStack itemStack, LootContext context) implements LootFunction.Context {\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return context.get(trait);\n        }\n    }\n\n    private static boolean fails(@Nullable List<Predicate> predicates, LootContext context) {\n        if (predicates == null) return false;\n        for (Predicate predicate : predicates) {\n            if (!predicate.test(context)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private void addEntries(LootContext context, LootTable.Pool.Entry entry, Consumer<LootTable.Pool.Entry.ItemGenerator> out) {\n        if (fails(entry.conditions(), context)) return;\n        switch (entry.type().toString()) {\n            case \"minecraft:item\", \"minecraft:tag\", \"minecraft:dynamic\", \"minecraft:empty\" -> out.accept((LootTable.Pool.Entry.ItemGenerator) entry);\n            case \"minecraft:loot_table\" -> // TODO: recursive loot tables\n                    Logger.debug(\"Recursive loot tables are not yet supported\");\n            case \"minecraft:group\" -> {\n                LootTable.Pool.Entry.Group group = (LootTable.Pool.Entry.Group) entry;\n                for (LootTable.Pool.Entry groupEntry : group.children()) {\n                    addEntries(context, groupEntry, out);\n                }\n            }\n            case \"minecraft:alternatives\" -> {\n                LootTable.Pool.Entry.Alternatives alternatives = (LootTable.Pool.Entry.Alternatives) entry;\n                for (LootTable.Pool.Entry alternative : alternatives.children()) {\n                    if (fails(alternative.conditions(), context)) continue;\n                    addEntries(context, alternative, out);\n                    break;\n                }\n            }\n            case \"minecraft:sequence\" -> {\n                LootTable.Pool.Entry.Sequence sequence = (LootTable.Pool.Entry.Sequence) entry;\n                for (LootTable.Pool.Entry alternative : sequence.children()) {\n                    if (fails(alternative.conditions(), context)) break;\n                    addEntries(context, alternative, out);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocks.java",
    "content": "package net.minestom.vanilla.blocks;\n\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectMap;\nimport it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.GameMode;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.player.PlayerBlockBreakEvent;\nimport net.minestom.server.event.player.PlayerBlockPlaceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.blocks.behaviours.*;\nimport net.minestom.vanilla.blocks.behaviours.oxidisable.OxidatableBlockBehaviour;\nimport net.minestom.vanilla.blocks.behaviours.oxidisable.WaxedBlockBehaviour;\nimport net.minestom.vanilla.blocks.behaviours.recipe.*;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdatable;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdateManager;\nimport net.minestom.vanilla.datapack.DatapackLoadingFeature;\nimport net.minestom.vanilla.randomticksystem.RandomTickManager;\nimport net.minestom.vanilla.randomticksystem.RandomTickable;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\n/**\n * All blocks available in the vanilla reimplementation\n */\npublic enum VanillaBlocks {\n\n    SAND(Block.SAND, GravityBlockBehaviour::new),\n    RED_SAND(Block.RED_SAND, GravityBlockBehaviour::new),\n    GRAVEL(Block.GRAVEL, GravityBlockBehaviour::new),\n\n    // Start of concrete powders\n    WHITE_CONCRETE_POWDER(Block.WHITE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.WHITE_CONCRETE)),\n    BLACK_CONCRETE_POWDER(Block.BLACK_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BLACK_CONCRETE)),\n    LIGHT_BLUE_CONCRETE_POWDER(Block.LIGHT_BLUE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIGHT_BLUE_CONCRETE)),\n    BLUE_CONCRETE_POWDER(Block.BLUE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BLUE_CONCRETE)),\n    RED_CONCRETE_POWDER(Block.RED_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.RED_CONCRETE)),\n    GREEN_CONCRETE_POWDER(Block.GREEN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.GREEN_CONCRETE)),\n    YELLOW_CONCRETE_POWDER(Block.YELLOW_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.YELLOW_CONCRETE)),\n    PURPLE_CONCRETE_POWDER(Block.PURPLE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.PURPLE_CONCRETE)),\n    MAGENTA_CONCRETE_POWDER(Block.MAGENTA_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.MAGENTA_CONCRETE)),\n    CYAN_CONCRETE_POWDER(Block.CYAN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.CYAN_CONCRETE)),\n    PINK_CONCRETE_POWDER(Block.PINK_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.PINK_CONCRETE)),\n    GRAY_CONCRETE_POWDER(Block.GRAY_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.GRAY_CONCRETE)),\n    LIGHT_GRAY_CONCRETE_POWDER(Block.LIGHT_GRAY_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIGHT_GRAY_CONCRETE)),\n    ORANGE_CONCRETE_POWDER(Block.ORANGE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.ORANGE_CONCRETE)),\n    BROWN_CONCRETE_POWDER(Block.BROWN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BROWN_CONCRETE)),\n    LIME_CONCRETE_POWDER(Block.LIME_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIME_CONCRETE)),\n    // End of concrete powders\n\n    // Start of oxidisable copper\n    // Blocks\n    COPPER_BLOCK(Block.COPPER_BLOCK, (context) ->           new OxidatableBlockBehaviour(context, Block.COPPER_BLOCK, Block.EXPOSED_COPPER, Block.WAXED_COPPER_BLOCK, 0)),\n    EXPOSED_COPPER(Block.EXPOSED_COPPER, (context) ->       new OxidatableBlockBehaviour(context, Block.COPPER_BLOCK, Block.WEATHERED_COPPER, Block.WAXED_EXPOSED_COPPER, 1)),\n    WEATHERED_COPPER(Block.WEATHERED_COPPER, (context) ->   new OxidatableBlockBehaviour(context, Block.EXPOSED_COPPER, Block.OXIDIZED_COPPER, Block.WAXED_WEATHERED_COPPER, 2)),\n    OXIDIZED_COPPER(Block.OXIDIZED_COPPER, (context) ->     new OxidatableBlockBehaviour(context, Block.WEATHERED_COPPER, Block.OXIDIZED_COPPER, Block.WAXED_OXIDIZED_COPPER, 3)),\n    // Cut Blocks\n    CUT_COPPER(Block.CUT_COPPER, (context) ->                     new OxidatableBlockBehaviour(context, Block.CUT_COPPER, Block.EXPOSED_CUT_COPPER, Block.WAXED_CUT_COPPER, 0)),\n    EXPOSED_CUT_COPPER(Block.EXPOSED_CUT_COPPER, (context) ->     new OxidatableBlockBehaviour(context, Block.CUT_COPPER, Block.WEATHERED_CUT_COPPER, Block.WAXED_EXPOSED_CUT_COPPER, 1)),\n    WEATHERED_CUT_COPPER(Block.WEATHERED_CUT_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER, Block.OXIDIZED_CUT_COPPER, Block.WAXED_WEATHERED_CUT_COPPER, 2)),\n    OXIDIZED_CUT_COPPER(Block.OXIDIZED_CUT_COPPER, (context) ->   new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER, Block.OXIDIZED_CUT_COPPER, Block.WAXED_OXIDIZED_CUT_COPPER, 3)),\n    // Stairs\n    CUT_COPPER_STAIRS(Block.CUT_COPPER_STAIRS, (context) ->                     new OxidatableBlockBehaviour(context, Block.CUT_COPPER_STAIRS, Block.EXPOSED_CUT_COPPER_STAIRS, Block.WAXED_CUT_COPPER_STAIRS, 0)),\n    EXPOSED_CUT_COPPER_STAIRS(Block.EXPOSED_CUT_COPPER_STAIRS, (context) ->     new OxidatableBlockBehaviour(context, Block.CUT_COPPER_STAIRS, Block.WEATHERED_CUT_COPPER_STAIRS, Block.WAXED_EXPOSED_CUT_COPPER_STAIRS, 1)),\n    WEATHERED_CUT_COPPER_STAIRS(Block.WEATHERED_CUT_COPPER_STAIRS, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_STAIRS, Block.OXIDIZED_CUT_COPPER_STAIRS, Block.WAXED_WEATHERED_CUT_COPPER_STAIRS, 2)),\n    OXIDIZED_CUT_COPPER_STAIRS(Block.OXIDIZED_CUT_COPPER_STAIRS, (context) ->   new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_STAIRS, Block.OXIDIZED_CUT_COPPER_STAIRS, Block.WAXED_OXIDIZED_CUT_COPPER_STAIRS, 3)),\n    // Slabs\n    CUT_COPPER_SLAB(Block.CUT_COPPER_SLAB, (context) ->                     new OxidatableBlockBehaviour(context, Block.CUT_COPPER_SLAB, Block.EXPOSED_CUT_COPPER_SLAB, Block.WAXED_CUT_COPPER_SLAB, 0)),\n    EXPOSED_CUT_COPPER_SLAB(Block.EXPOSED_CUT_COPPER_SLAB, (context) ->     new OxidatableBlockBehaviour(context, Block.CUT_COPPER_SLAB, Block.WEATHERED_CUT_COPPER_SLAB, Block.WAXED_EXPOSED_CUT_COPPER_SLAB, 1)),\n    WEATHERED_CUT_COPPER_SLAB(Block.WEATHERED_CUT_COPPER_SLAB, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_SLAB, Block.OXIDIZED_CUT_COPPER_SLAB, Block.WAXED_WEATHERED_CUT_COPPER_SLAB, 2)),\n    OXIDIZED_CUT_COPPER_SLAB(Block.OXIDIZED_CUT_COPPER_SLAB, (context) ->   new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_SLAB, Block.OXIDIZED_CUT_COPPER_SLAB, Block.WAXED_OXIDIZED_CUT_COPPER_SLAB, 3)),\n    // End of copper\n\n    // Start of waxed copper\n    // Blocks\n    WAXED_COPPER_BLOCK(Block.WAXED_COPPER_BLOCK, (context) ->         new WaxedBlockBehaviour(context, Block.COPPER_BLOCK, 0)),\n    WAXED_EXPOSED_COPPER(Block.WAXED_EXPOSED_COPPER, (context) ->     new WaxedBlockBehaviour(context, Block.EXPOSED_COPPER, 1)),\n    WAXED_WEATHERED_COPPER(Block.WAXED_WEATHERED_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_COPPER, 2)),\n    WAXED_OXIDIZED_COPPER(Block.WAXED_OXIDIZED_COPPER, (context) ->   new WaxedBlockBehaviour(context, Block.OXIDIZED_COPPER, 3)),\n    // Cut Blocks\n    WAXED_CUT_COPPER(Block.WAXED_CUT_COPPER, (context) ->                     new WaxedBlockBehaviour(context, Block.CUT_COPPER, 0)),\n    WAXED_EXPOSED_CUT_COPPER(Block.WAXED_EXPOSED_CUT_COPPER, (context) ->     new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER, 1)),\n    WAXED_WEATHERED_CUT_COPPER(Block.WAXED_WEATHERED_CUT_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER, 2)),\n    WAXED_OXIDIZED_CUT_COPPER(Block.WAXED_OXIDIZED_CUT_COPPER, (context) ->   new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER, 3)),\n    // Stairs\n    WAXED_CUT_COPPER_STAIRS(Block.WAXED_CUT_COPPER_STAIRS, (context) ->                     new WaxedBlockBehaviour(context, Block.CUT_COPPER_STAIRS, 0)),\n    WAXED_EXPOSED_CUT_COPPER_STAIRS(Block.WAXED_EXPOSED_CUT_COPPER_STAIRS, (context) ->     new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_STAIRS, 1)),\n    WAXED_WEATHERED_CUT_COPPER_STAIRS(Block.WAXED_WEATHERED_CUT_COPPER_STAIRS, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_STAIRS, 2)),\n    WAXED_OXIDIZED_CUT_COPPER_STAIRS(Block.WAXED_OXIDIZED_CUT_COPPER_STAIRS, (context) ->   new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER_STAIRS, 3)),\n    // Slabs\n    WAXED_CUT_COPPER_SLAB(Block.WAXED_CUT_COPPER_SLAB, (context) ->                     new WaxedBlockBehaviour(context, Block.CUT_COPPER_SLAB, 0)),\n    WAXED_EXPOSED_CUT_COPPER_SLAB(Block.WAXED_EXPOSED_CUT_COPPER_SLAB, (context) ->     new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_SLAB, 1)),\n    WAXED_WEATHERED_CUT_COPPER_SLAB(Block.WAXED_WEATHERED_CUT_COPPER_SLAB, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_SLAB, 2)),\n    WAXED_OXIDIZED_CUT_COPPER_SLAB(Block.WAXED_OXIDIZED_CUT_COPPER_SLAB, (context) ->   new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER_SLAB, 3)),\n    // End of waxed copper\n\n    // Start of beds\n    WHITE_BED(Block.WHITE_BED, BedBlockBehaviour::new),\n    BLACK_BED(Block.BLACK_BED, BedBlockBehaviour::new),\n    LIGHT_BLUE_BED(Block.LIGHT_BLUE_BED, BedBlockBehaviour::new),\n    BLUE_BED(Block.BLUE_BED, BedBlockBehaviour::new),\n    RED_BED(Block.RED_BED, BedBlockBehaviour::new),\n    GREEN_BED(Block.GREEN_BED, BedBlockBehaviour::new),\n    YELLOW_BED(Block.YELLOW_BED, BedBlockBehaviour::new),\n    PURPLE_BED(Block.PURPLE_BED, BedBlockBehaviour::new),\n    MAGENTA_BED(Block.MAGENTA_BED, BedBlockBehaviour::new),\n    CYAN_BED(Block.CYAN_BED, BedBlockBehaviour::new),\n    PINK_BED(Block.PINK_BED, BedBlockBehaviour::new),\n    GRAY_BED(Block.GRAY_BED, BedBlockBehaviour::new),\n    LIGHT_GRAY_BED(Block.LIGHT_GRAY_BED, BedBlockBehaviour::new),\n    ORANGE_BED(Block.ORANGE_BED, BedBlockBehaviour::new),\n    BROWN_BED(Block.BROWN_BED, BedBlockBehaviour::new),\n    LIME_BED(Block.LIME_BED, BedBlockBehaviour::new),\n    // End of beds\n\n    FIRE(Block.FIRE, FireBlockBehaviour::new),\n    NETHER_PORTAL(Block.NETHER_PORTAL, NetherPortalBlockBehaviour::new),\n    END_PORTAL(Block.END_PORTAL, EndPortalBlockBehaviour::new),\n\n    TNT(Block.TNT, TNTBlockBehaviour::new),\n\n    CHEST(Block.CHEST, ChestBlockBehaviour::new),\n    TRAPPED_CHEST(Block.TRAPPED_CHEST, TrappedChestBlockBehaviour::new),\n    ENDER_CHEST(Block.ENDER_CHEST, EnderChestBlockBehaviour::new),\n    JUKEBOX(Block.JUKEBOX, JukeboxBlockBehaviour::new),\n\n    // recipes\n    CRAFTING_TABLE(Block.CRAFTING_TABLE, CraftingTableBehaviour::new),\n    FURNACE(Block.FURNACE, FurnaceBehaviour::new),\n    SMOKER(Block.SMOKER, SmokerBehaviour::new),\n    BLAST_FURNACE(Block.BLAST_FURNACE, BlastingFurnaceBehaviour::new),\n\n    STONE_CUTTER(Block.STONECUTTER, StonecutterBehaviour::new),\n    CAMPFIRE(Block.CAMPFIRE, CampfireBehaviour::new),\n    SOUL_CAMPFIRE(Block.SOUL_CAMPFIRE, CampfireBehaviour::new),\n    SMITHING_TABLE(Block.SMITHING_TABLE, SmithingTableBehaviour::new),\n\n    // Start of cakes\n    CAKE(Block.CAKE, CakeBlockBehaviour::new),\n    CANDLE_CAKE(Block.CANDLE_CAKE, CakeBlockBehaviour::new),\n    WHITE_CANDLE_CAKE(Block.WHITE_CANDLE_CAKE, CakeBlockBehaviour::new),\n    ORANGE_CANDLE_CAKE(Block.ORANGE_CANDLE_CAKE, CakeBlockBehaviour::new),\n    MAGENTA_CANDLE_CAKE(Block.MAGENTA_CANDLE_CAKE, CakeBlockBehaviour::new),\n    LIGHT_BLUE_CANDLE_CAKE(Block.LIGHT_BLUE_CANDLE_CAKE, CakeBlockBehaviour::new),\n    YELLOW_CANDLE_CAKE(Block.YELLOW_CANDLE_CAKE, CakeBlockBehaviour::new),\n    LIME_CANDLE_CAKE(Block.LIME_CANDLE_CAKE, CakeBlockBehaviour::new),\n    PINK_CANDLE_CAKE(Block.PINK_CANDLE_CAKE, CakeBlockBehaviour::new),\n    GRAY_CANDLE_CAKE(Block.GRAY_CANDLE_CAKE, CakeBlockBehaviour::new),\n    LIGHT_GRAY_CANDLE_CAKE(Block.LIGHT_GRAY_CANDLE_CAKE, CakeBlockBehaviour::new),\n    CYAN_CANDLE_CAKE(Block.CYAN_CANDLE_CAKE, CakeBlockBehaviour::new),\n    PURPLE_CANDLE_CAKE(Block.PURPLE_CANDLE_CAKE, CakeBlockBehaviour::new),\n    BLUE_CANDLE_CAKE(Block.BLUE_CANDLE_CAKE, CakeBlockBehaviour::new),\n    BROWN_CANDLE_CAKE(Block.BROWN_CANDLE_CAKE, CakeBlockBehaviour::new),\n    GREEN_CANDLE_CAKE(Block.GREEN_CANDLE_CAKE, CakeBlockBehaviour::new),\n    BLACK_CANDLE_CAKE(Block.BLACK_CANDLE_CAKE, CakeBlockBehaviour::new)\n    // End of cakes\n\n    ;\n    private final short stateId;\n    private final @NotNull Context2Handler context2handler;\n\n    VanillaBlocks(@NotNull Block minestomBlock, @NotNull Context2Handler context2handler) {\n        this.stateId = (short) minestomBlock.stateId();\n        this.context2handler = context -> {\n            if (context.stateId() != minestomBlock.stateId()) {\n                throw new IllegalStateException(\"Block registry mismatch. Registered block: \" + minestomBlock.stateId() +\n                        \" !=  Given block:\" + context.stateId());\n            }\n            return context2handler.apply(context);\n        };\n    }\n\n    interface Context2Handler {\n        @NotNull VanillaBlockBehaviour apply(@NotNull BlockContext context);\n    }\n\n    /**\n     * Used to provide context for creating block handlers\n     */\n    public interface BlockContext {\n        short stateId();\n\n        @NotNull VanillaReimplementation vri();\n    }\n\n    /**\n     * Creates a block handler from the context\n     *\n     * @param context the context\n     * @return the block handler\n     */\n    public @NotNull VanillaBlockBehaviour create(@NotNull BlockContext context) {\n        return context2handler.apply(context);\n    }\n\n    /**\n     * Register all vanilla blocks. ConnectionManager will handle replacing the basic\n     * block with its custom variant.\n     *\n     * @param vri the vanilla reimplementation object\n     */\n    public static void registerAll(@NotNull VanillaReimplementation vri) {\n\n        EventNode<Event> events = EventNode.all(\"vanilla-blocks\");\n\n        // block loot\n        VanillaBlockLoot loot = new VanillaBlockLoot(vri, vri.feature(DatapackLoadingFeature.class).current());\n        events.addListener(EventListener.builder(PlayerBlockBreakEvent.class)\n                .filter(event -> !event.isCancelled())\n                .filter(event -> event.getPlayer().getGameMode() != GameMode.CREATIVE)\n                .handler(loot::spawnLoot)\n                .build());\n\n        Short2ObjectMap<VanillaBlockBehaviour> stateId2behaviour = new Short2ObjectOpenHashMap<>();\n\n        for (VanillaBlocks vb : values()) {\n            BlockContext context = new BlockContext() {\n                @Override\n                public short stateId() {\n                    return vb.stateId;\n                }\n\n                @Override\n                public @NotNull VanillaReimplementation vri() {\n                    return vri;\n                }\n            };\n            VanillaBlockBehaviour behaviour = vb.context2handler.apply(context);\n\n            for (Block possibleState : Objects.requireNonNull(Block.fromStateId(vb.stateId)).possibleStates()) {\n                short possibleStateId = (short) possibleState.stateId();\n                stateId2behaviour.put(possibleStateId, behaviour);\n            }\n\n            if (behaviour instanceof BlockUpdatable updatable)\n                BlockUpdateManager.registerUpdatable(vb.stateId, updatable);\n\n            if (behaviour instanceof RandomTickable randomTickable)\n                RandomTickManager.registerRandomTickable(vb.stateId, randomTickable);\n        }\n\n        registerEvents(events, stateId2behaviour);\n\n        vri.process().eventHandler().addChild(events);\n    }\n\n    private static void registerEvents(EventNode<Event> node, Short2ObjectMap<VanillaBlockBehaviour> behaviours) {\n        node.addListener(EventListener.builder(PlayerBlockPlaceEvent.class)\n                .filter(event -> behaviours.containsKey((short) event.getBlock().stateId()))\n                .handler(event -> {\n                    short stateId = (short) event.getBlock().stateId();\n                    Block block = Objects.requireNonNull(Block.fromStateId(stateId));\n                    var behaviour = behaviours.get(stateId);\n\n                    behaviour.onPlace(new PlayerPlacement(event));\n                    Block blockToPlace = event.getBlock();\n                    if (blockToPlace.compare(block)) {\n                        event.setBlock(blockToPlace.withHandler(behaviour));\n                    }\n                })\n                .build());\n    }\n\n    private record PlayerPlacement(PlayerBlockPlaceEvent event) implements VanillaBlockBehaviour.VanillaPlacement,\n            VanillaBlockBehaviour.VanillaPlacement.HasPlayer {\n\n        @Override\n        public @NotNull Block blockToPlace() {\n            return event.getBlock();\n        }\n\n        @Override\n        public @NotNull Instance instance() {\n            return event.getInstance();\n        }\n\n        @Override\n        public @NotNull Point position() {\n            return event.getBlockPosition();\n        }\n\n        @Override\n        public void blockToPlace(@NotNull Block newBlock) {\n            event.setBlock(newBlock);\n        }\n\n        @Override\n        public @NotNull Player player() {\n            return event.getPlayer();\n        }\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocksFeature.java",
    "content": "package net.minestom.vanilla.blocks;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.event.player.PlayerBlockPlaceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.BlockUpdateFeature;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.datapack.DatapackLoadingFeature;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class VanillaBlocksFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        VanillaReimplementation vri = context.vri();\n        VanillaBlocks.registerAll(vri);\n\n        vri.process().eventHandler().addListener(PlayerBlockPlaceEvent.class, event -> {\n            Block block = event.getBlock();\n            Instance instance = event.getInstance();\n            Point position = event.getBlockPosition();\n            AtomicReference<Block> blockToPlace = new AtomicReference<>(block);\n\n            if (block.handler() instanceof VanillaBlockBehaviour vanillaHandler) {\n                // Create the new placement object\n                VanillaBlockBehaviour.VanillaPlacement placement = new PlacementImpl(blockToPlace, instance, position);\n                vanillaHandler.onPlace(placement);\n            }\n\n            event.setBlock(blockToPlace.get());\n        });\n    }\n\n    private record PlacementImpl(AtomicReference<Block> blockToPlaceRef, Instance instance, Point position) implements VanillaBlockBehaviour.VanillaPlacement {\n        @Override\n        public @NotNull Block blockToPlace() {\n            return blockToPlaceRef.get();\n        }\n\n        @Override\n        public @NotNull Instance instance() {\n            return instance;\n        }\n\n        @Override\n        public @NotNull Point position() {\n            return position;\n        }\n\n        @Override\n        public void blockToPlace(@NotNull Block newBlock) {\n            blockToPlaceRef.getAndSet(newBlock);\n            // TODO: Run vanillaHandler.onPlace again on the new block if it's a vanilla block\n        }\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:blocks\");\n    }\n\n    @Override\n    public @NotNull Set<Class<? extends VanillaReimplementation.Feature>> dependencies() {\n        return Set.of(BlockUpdateFeature.class, DatapackLoadingFeature.class);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/BedBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.EntityPose;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.metadata.PlayerMeta;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.utils.Direction;\nimport net.minestom.server.utils.MathUtils;\nimport net.minestom.server.utils.time.TimeUnit;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.instance.VanillaExplosion;\nimport org.jetbrains.annotations.NotNull;\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic class BedBlockBehaviour extends VanillaBlockBehaviour {\n    public BedBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n//    @Override\n//    protected BlockPropertyList createPropertyValues() {\n//        return new BlockPropertyList().facingProperty(\"facing\").booleanProperty(\"occupied\").property(\"part\", \"foot\", \"head\");\n//    }\n\n\n    @Override\n    public void onPlace(@NotNull VanillaPlacement placement) {\n        if (!(placement instanceof VanillaPlacement.HasPlayer hasPlayer)) {\n            return;\n        }\n\n        Instance instance = placement.instance();\n        Point pos = placement.position();\n        Player player = hasPlayer.player();\n\n        ItemStack itemStack = player.getItemInMainHand(); // TODO: Hand determination\n\n        Block bedBlock = itemStack.material().block();\n\n        // TODO: Proper block placement management\n        Direction playerDirection = MathUtils.getHorizontalDirection(player.getPosition().yaw());\n\n        Point bedHeadPosition = pos.add(playerDirection.normalX(), playerDirection.normalY(), playerDirection.normalZ());\n        Block blockAtPotentialBedHead = instance.getBlock(bedHeadPosition);\n\n        if (isReplaceable(blockAtPotentialBedHead)) {\n            Block foot = placeBed(instance, bedBlock, bedHeadPosition, playerDirection);\n            placement.blockToPlace(foot);\n        } else {\n            placement.blockToPlace(placement.instance().getBlock(placement.position()));\n        }\n\n    }\n\n    private boolean isReplaceable(Block blockAtPosition) {\n        return blockAtPosition.isAir() || blockAtPosition.isLiquid();\n    }\n\n    private Block placeBed(Instance instance, Block bedBlock, Point headPosition, Direction facing) {\n        Block correctFacing = bedBlock.withProperty(\"facing\", facing.name().toLowerCase());\n\n        Block footBlock = correctFacing.withProperty(\"part\", \"foot\");\n        Block headBlock = correctFacing.withProperty(\"part\", \"head\").withHandler(new BedBlockBehaviour(this.context));\n        instance.setBlock(headPosition, headBlock);\n        return footBlock;\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Player player = interaction.getPlayer();\n        var dimensionKey = instance.getDimensionType();\n        DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(dimensionKey);\n\n        if (dimension.bedWorks()) {\n            // TODO: make player sleep\n            // TODO: checks for mobs\n            // TODO: check for day\n\n            // If time is not day\n//            long dayTime = instance.getTime() % 24000L;\n//            if (!(dayTime > 12541L && dayTime < 23458L)) {\n//                return true;\n//            }\n\n            // Make player sleep\n            PlayerMeta meta = player.getPlayerMeta();\n            meta.setBedInWhichSleepingPosition(pos);\n            meta.setPose(EntityPose.SLEEPING);\n\n            // Schedule player getting out of bed\n            MinecraftServer.getSchedulerManager().buildTask(() -> {\n                        if (!player.getPlayerConnection().isOnline()) {\n                            return;\n                        }\n\n                        meta.setBedInWhichSleepingPosition(null);\n                        meta.setPose(EntityPose.STANDING);\n                    })\n                    .delay(101, TimeUnit.SERVER_TICK)\n                    .schedule();\n            return true;\n        }\n\n        VanillaExplosion.builder(pos.add(0.5), 5)\n                .isFlaming(true)\n                .build()\n                .apply(instance);\n        return true;\n    }\n\n    @Override\n    public void onDestroy(@NotNull Destroy destroy) {\n        Instance instance = destroy.getInstance();\n        Block block = destroy.getBlock();\n        Point pos = destroy.getBlockPosition();\n\n        boolean isHead = \"head\".equals(block.getProperty(\"part\"));\n        Direction facing = Direction.valueOf(block.getProperty(\"facing\").toUpperCase());\n\n        if (isHead) {\n            facing = facing.opposite();\n        }\n\n        Point otherPartPosition = pos.add(facing.normalX(), facing.normalY(), facing.normalZ());\n        instance.setBlock(pos, Block.AIR);\n        instance.setBlock(otherPartPosition, Block.AIR);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/CakeBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\n\nimport static java.util.Map.entry;\n\npublic class CakeBlockBehaviour extends VanillaBlockBehaviour {\n\n    private static final Map<Block, Material> candleCakes = Map.ofEntries(\n            entry(Block.CANDLE_CAKE, Material.CANDLE),\n            entry(Block.WHITE_CANDLE_CAKE, Material.WHITE_CANDLE),\n            entry(Block.ORANGE_CANDLE_CAKE, Material.ORANGE_CANDLE),\n            entry(Block.MAGENTA_CANDLE_CAKE, Material.MAGENTA_CANDLE),\n            entry(Block.LIGHT_BLUE_CANDLE_CAKE, Material.LIGHT_BLUE_CANDLE),\n            entry(Block.YELLOW_CANDLE_CAKE, Material.YELLOW_CANDLE),\n            entry(Block.LIME_CANDLE_CAKE, Material.LIME_CANDLE),\n            entry(Block.PINK_CANDLE_CAKE, Material.PINK_CANDLE),\n            entry(Block.GRAY_CANDLE_CAKE, Material.GRAY_CANDLE),\n            entry(Block.LIGHT_GRAY_CANDLE_CAKE, Material.LIGHT_GRAY_CANDLE),\n            entry(Block.CYAN_CANDLE_CAKE, Material.CYAN_CANDLE),\n            entry(Block.PURPLE_CANDLE_CAKE, Material.PURPLE_CANDLE),\n            entry(Block.BLUE_CANDLE_CAKE, Material.BLUE_CANDLE),\n            entry(Block.BROWN_CANDLE_CAKE, Material.BROWN_CANDLE),\n            entry(Block.GREEN_CANDLE_CAKE, Material.GREEN_CANDLE),\n            entry(Block.BLACK_CANDLE_CAKE, Material.BLACK_CANDLE)\n    );\n\n    private static final ItemStack flint_and_steel = ItemStack.of(Material.FLINT_AND_STEEL);\n\n    public CakeBlockBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context);\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        Player player = interaction.getPlayer();\n        Block block = interaction.getBlock();\n        Point point = interaction.getBlockPosition();\n        Instance instance = interaction.getInstance();\n\n        int food = player.getFood();\n        float saturation = player.getFoodSaturation();\n        ItemStack item = player.getItemInMainHand();\n\n        // Player is trying to light candle cake\n        if (item.isSimilar(flint_and_steel) && candleCakes.containsKey(block) &&\n                !Boolean.parseBoolean(block.getProperty(\"lit\"))) {\n            instance.setBlock(point, block.withProperty(\"lit\", \"true\"));\n\n            // TODO: Handle tool durability\n            return true;\n        }\n\n        // Player is eating cake\n        if (food < 20) {\n            tryDropCandle(block, instance, point);\n\n            // Update hunger values\n            int newFood = Math.max(20, food + 2);\n            float newSaturation = Math.max(20f, saturation + 0.4f);\n            player.setFood(newFood);\n            player.setFoodSaturation(newSaturation);\n        }\n        return true;\n    }\n\n    @Override\n    public void onDestroy(@NotNull Destroy destroy) {\n        Block block = destroy.getBlock();\n        Instance instance = destroy.getInstance();\n        Point point = destroy.getBlockPosition();\n\n        tryDropCandle(block, instance, point);\n    }\n\n    private void tryDropCandle(Block block, Instance instance, Point point) {\n        if (block != Block.CAKE) {\n            instance.setBlock(point, Block.CAKE.withProperty(\"bites\", \"1\"));\n            ItemStack candle = ItemStack.of(candleCakes.get(block));\n            new ItemEntity(candle).setInstance(instance);\n        }\n    }\n}"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ChestBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic class ChestBlockBehaviour extends InventoryBlockBehaviour {\n    public ChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context, InventoryType.CHEST_3_ROW, Component.text(\"Chest\"));\n    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ConcretePowderBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\n// TODO: When placing concrete powder in water, it turns to the solid block correctly, however it falls like a regular concrete powder block\npublic class ConcretePowderBlockBehaviour extends GravityBlockBehaviour {\n    private final Block solidifiedBlock;\n\n    public ConcretePowderBlockBehaviour(@NotNull VanillaBlocks.BlockContext context, Block solidifiedBlock) {\n        super(context);\n        this.solidifiedBlock = solidifiedBlock;\n    }\n\n    @Override\n    public void onPlace(@NotNull VanillaBlockBehaviour.VanillaPlacement placement) {\n        super.onPlace(placement);\n        tryConvert(placement.instance(), placement.position());\n    }\n\n    @Override\n    public void tick(@NotNull Tick tick) {\n        tryConvert(tick.getInstance(), tick.getBlockPosition());\n    }\n\n    private void tryConvert(Instance instance, Point blockPosition) {\n\n        int x = blockPosition.blockX();\n        int y = blockPosition.blockY();\n        int z = blockPosition.blockZ();\n\n        // TODO: support block tags\n\n        if (\n                instance.getBlock(x, y + 1, z).compare(Block.WATER) || // above\n                        instance.getBlock(x - 1, y, z).compare(Block.WATER) || // west\n                        instance.getBlock(x + 1, y, z).compare(Block.WATER) || // east\n                        instance.getBlock(x, y, z - 1).compare(Block.WATER) || // north\n                        instance.getBlock(x, y, z + 1).compare(Block.WATER)    // south\n        ) {\n            instance.setBlock(blockPosition, solidifiedBlock);\n        }\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EndPortalBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.dimensions.VanillaDimensionTypes;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Optional;\n\npublic class EndPortalBlockBehaviour extends VanillaBlockBehaviour {\n    public EndPortalBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n//    @Override\n//    protected BlockPropertyList createPropertyValues() {\n//        return new BlockPropertyList();\n//    }\n\n    @Override\n    public void onTouch(@NotNull Touch touch) {\n        Instance instance = touch.getInstance();\n        Entity touching = touch.getTouching();\n        var key = instance.getDimensionType();\n        DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(key);\n\n        DimensionType targetDimension = VanillaDimensionTypes.OVERWORLD;\n        Optional<Instance> potentialTargetInstance = MinecraftServer.getInstanceManager().getInstances().stream()\n                .filter(in -> {\n                    var key1 = in.getDimensionType();\n                    return MinecraftServer.getDimensionTypeRegistry().get(key1) == targetDimension;\n                })\n                .findFirst();\n\n        // TODO: event\n        if (potentialTargetInstance.isPresent()) {\n            Instance targetInstance = potentialTargetInstance.get();\n            Pos spawnPoint;\n            final int obsidianPlatformX = 100;\n            final int obsidianPlatformY = 48;\n            final int obsidianPlatformZ = 0;\n\n            if (targetDimension == VanillaDimensionTypes.OVERWORLD) { // teleport to spawn point\n                if (touching instanceof Player) {\n                    spawnPoint = ((Player) touching).getRespawnPoint();\n                } else { // TODO: world spawnpoint\n                    spawnPoint = new Pos(0, 80, 0);\n                }\n            } else {\n                // teleport to the obsidian platform, and recreate it if necessary\n                int yLevel = touching instanceof Player ? 49 : 50;\n                spawnPoint = new Pos(obsidianPlatformX, yLevel, obsidianPlatformZ);\n            }\n\n            if (targetDimension.effects().equals(\"the_end\")) {\n                for (int x = -1; x <= 1; x++) {\n                    for (int z = -1; z <= 1; z++) {\n                        targetInstance.loadChunk(obsidianPlatformX / 16 + x, obsidianPlatformZ / 16 + z);\n                    }\n                }\n\n                // clear 5x3x5 area around platform\n                for (int x = 0; x < 5; x++) {\n                    for (int z = 0; z < 5; z++) {\n                        for (int y = 0; y < 3; y++) {\n                            targetInstance.setBlock(obsidianPlatformX + x, obsidianPlatformY + y + 1, obsidianPlatformZ + z, Block.AIR);\n                        }\n                    }\n                }\n            }\n            touching.setInstance(targetInstance);\n            touching.teleport(spawnPoint);\n        }\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EnderChestBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.system.EnderChestSystem;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\n\npublic class EnderChestBlockBehaviour extends InventoryBlockBehaviour {\n    public EnderChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context, InventoryType.CHEST_3_ROW, Component.text(\"Ender Chest\"));\n    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return false;\n    }\n\n    @Override\n    protected List<ItemStack> getAllItems(Instance instance, Point pos, Player player) {\n        return EnderChestSystem.getInstance().getItems(player);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/FireBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.LivingEntity;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.system.NetherPortal;\nimport org.jetbrains.annotations.NotNull;\n\npublic class FireBlockBehaviour extends VanillaBlockBehaviour {\n    public FireBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n//    @Override\n//    protected BlockPropertyList createPropertyValues() {\n//        return new BlockPropertyList().intRange(\"age\", 0, 15);\n//    }\n\n    @Override\n    public void onTouch(@NotNull Touch touch) {\n        Entity touching = touch.getTouching();\n\n        if (!(touching instanceof LivingEntity livingEntity)) {\n            return;\n        }\n\n        if (livingEntity.isOnFire()) {\n            return;\n        }\n\n        livingEntity.damage(DamageType.IN_FIRE, 1.0f);\n        livingEntity.setFireTicks(8 * 20);\n    }\n\n    public void checkForPortal(Instance instance, Point pos, Block block) {\n        NetherPortal portal = NetherPortal.findPortalFrameFromFrameBlock(instance, pos);\n\n        if (portal == null) {\n            return;\n        }\n\n        if (portal.tryFillFrame(instance)) {\n            portal.register(instance);\n        }\n    }\n\n    @Override\n    public void onPlace(@NotNull VanillaPlacement placement) {\n        // check for Nether portal immediately\n        checkForPortal(placement.instance(), placement.position(), placement.blockToPlace());\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/GravityBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaRegistry;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdatable;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdateInfo;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdateManager;\nimport net.minestom.vanilla.entitymeta.EntityTags;\nimport org.jetbrains.annotations.NotNull;\n\npublic class GravityBlockBehaviour extends VanillaBlockBehaviour implements BlockUpdatable {\n    public GravityBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n    @Override\n    public void onPlace(@NotNull VanillaPlacement placement) {\n        Instance instance = placement.instance();\n        Point position = placement.position();\n        Block block = placement.blockToPlace();\n\n        if (checkFall(instance, position, block)) {\n            placement.blockToPlace(Block.AIR);\n        }\n    }\n\n    /**\n     * Checks if a block should fall\n     *\n     * @param instance the instance the block is in\n     * @param position the position of the block\n     * @return true if the block should fall\n     */\n    public boolean checkFall(Instance instance, Point position, Block block) {\n        Block below = instance.getBlock(position.blockX(), position.blockY() - 1, position.blockZ());\n\n        // Exit out now if block below is solid\n        if (below.isSolid()) {\n            return false;\n        }\n\n        // Schedule block update\n        BlockUpdateManager.from(instance).scheduleNeighborsUpdate(position, BlockUpdateInfo.MOVE_BLOCK());\n\n        // Create the context\n        Pos initialPosition = new Pos(position.x() + 0.5f, Math.round(position.y()), position.z() + 0.5f);\n        VanillaRegistry.EntityContext entityContext = context.vri().entityContext(EntityType.FALLING_BLOCK,\n                initialPosition, nbt -> nbt.setTag(EntityTags.FallingBlock.BLOCK, block));\n        Entity entity = context.vri().createEntityOrDummy(entityContext);\n\n        // Spawn the entity\n        entity.setInstance(instance, initialPosition);\n        return true;\n    }\n\n    @Override\n    public void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info) {\n        checkFall(instance, pos, instance.getBlock(pos));\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/InventoryBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.server.utils.Direction;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.UnknownNullability;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Base class for blocks with an inventory.\n * <p>\n * This class needs onPlace to be able to change the block being placed\n */\npublic abstract class InventoryBlockBehaviour extends VanillaBlockBehaviour {\n\n    public static final Tag<List<ItemStack>> TAG_ITEMS = Tag.ItemStack(\"vri:chest_items\").list();\n    protected static final Random rng = new Random();\n    protected final InventoryType type;\n    protected final Component title;\n\n    public InventoryBlockBehaviour(@NotNull VanillaBlocks.BlockContext context, InventoryType type, Component title) {\n        super(context);\n        this.type = type;\n        this.title = title;\n    }\n\n    @Override\n    public void onPlace(@NotNull VanillaPlacement placement) {\n        Block block = placement.blockToPlace();\n        Instance instance = placement.instance();\n        Point pos = placement.position();\n\n        @UnknownNullability List<ItemStack> items = block.getTag(TAG_ITEMS);\n\n        if (items != null) {\n            return;\n        }\n\n        ItemStack[] itemsArray = new ItemStack[type.getSize()];\n        Arrays.fill(itemsArray, ItemStack.AIR);\n\n        // Override the block to set\n        Block blockToSet = block.withTag(TAG_ITEMS, List.of(itemsArray));\n        placement.blockToPlace(blockToSet);\n    }\n\n    @Override\n    public void onDestroy(@NotNull Destroy destroy) {\n        Instance instance = destroy.getInstance();\n        Point pos = destroy.getBlockPosition();\n        Block block = destroy.getBlock();\n\n        // TODO: Introduce a way to get the block this is getting replaced with, enabling us to remove the tick delay.\n        destroy.getInstance().scheduleNextTick(ignored -> {\n            if (instance.getBlock(pos).compare(block)) {\n                // Same block, don't remove chest inventory\n                return;\n            }\n\n            // Different block, remove chest inventory\n            List<ItemStack> items = BlockInventory.remove(instance, pos);\n\n            if (!dropContentsOnDestroy()) {\n                return;\n            }\n\n            for (ItemStack item : items) {\n\n                if (item == null) {\n                    continue;\n                }\n\n                ItemEntity entity = new ItemEntity(item);\n\n                entity.setInstance(destroy.getInstance());\n                entity.teleport(new Pos(pos.x() + rng.nextDouble(), pos.y() + .5f, pos.z() + rng.nextDouble()));\n            }\n        });\n    }\n\n//    @Override\n//    public short getVisualBlockForPlacement(Player player, Player.Hand hand, BlockPosition position) {\n//        // TODO: handle double chests\n//        boolean waterlogged = Block.fromStateId(player.getInstance().getBlockStateId(position.getX(), position.getY(), position.getZ())) == Block.WATER;\n//        float yaw = player.getPosition().getYaw();\n//        Direction direction = MathUtils.getHorizontalDirection(yaw).opposite();\n//        return getBaseBlockState().with(\"facing\", direction.name().toLowerCase()).with(\"waterlogged\", String.valueOf(waterlogged)).getBlockId();\n//    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        // TODO: handle double chests\n        // TODO: Handle crouching players\n\n        Block block = interaction.getBlock();\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Player player = interaction.getPlayer();\n\n        Block above = instance.getBlock(pos.blockX(), pos.blockY() + 1, pos.blockZ());\n\n        if (above.isSolid()) { // FIXME: chests below transparent blocks cannot be opened\n            return false;\n        }\n\n        Inventory chestInventory = BlockInventory.from(instance, pos, type, title);\n        player.openInventory(chestInventory);\n        return true;\n    }\n\n    public abstract boolean dropContentsOnDestroy();\n\n    /**\n     * Gets the items in this block only\n     *\n     * @param block the block\n     * @return the items\n     */\n    protected @NotNull List<ItemStack> getItems(Block block) {\n        List<ItemStack> items = block.getTag(TAG_ITEMS);\n        if (items == null) {\n            throw new IllegalStateException(\"Chest block has no items\");\n        }\n        if (items.size() != this.type.getSize()) {\n            throw new IllegalStateException(\"Invalid items size\");\n        }\n        return items;\n    }\n\n    /**\n     * Sets the items in this block only\n     *\n     * @param block the block\n     * @param items the items\n     */\n    protected Block setItems(Block block, List<ItemStack> items) {\n        if (items.size() != this.type.getSize()) {\n            throw new IllegalStateException(\"Invalid items size\");\n        }\n        return block.withTag(TAG_ITEMS, items);\n    }\n\n    /**\n     * Gets all items represented by this position in this instance\n     *\n     * @param instance the instance\n     * @param pos      the position\n     * @return all items in the position in the instance\n     */\n    protected List<ItemStack> getAllItems(Instance instance, Point pos, Player player) {\n        Block block = instance.getBlock(pos);\n        List<ItemStack> items = new ArrayList<>(getItems(block));\n\n        Point positionOfOtherChest = pos;\n        Direction facing = Direction.valueOf(block.getProperty(\"facing\").toUpperCase());\n        String type = block.getProperty(\"type\");\n\n        switch (type) {\n            case \"single\" -> {\n                return List.copyOf(items);\n            }\n            case \"left\" -> positionOfOtherChest = positionOfOtherChest.add(-facing.normalZ(), 0, facing.normalX());\n            case \"right\" -> positionOfOtherChest = positionOfOtherChest.add(facing.normalZ(), 0, -facing.normalX());\n            default -> throw new IllegalArgumentException(\"Invalid chest type: \" + type);\n        }\n\n        Block otherBlock = instance.getBlock(positionOfOtherChest);\n        BlockHandler handler = otherBlock.handler();\n\n        if (handler instanceof InventoryBlockBehaviour chestLike) {\n            items.addAll(chestLike.getItems(otherBlock));\n        }\n\n        return List.copyOf(items);\n    }\n\n//    @Override\n//    public Data readBlockEntity(NBTCompound nbt, Instance instance, BlockPosition position, Data originalData) {\n//        ChestBlockEntity data;\n//        if (originalData instanceof ChestBlockEntity) {\n//            data = (ChestBlockEntity) originalData;\n//        } else {\n//            data = new ChestBlockEntity(position.copy());\n//        }\n//\n//        // TODO: CustomName\n//        // TODO: Lock\n//        // TODO: LootTable\n//        // TODO: LootTableSeed\n//\n//        if (nbt.containsKey(\"Items\")) {\n//            NBTUtils.loadAllItems(nbt.getList(\"Items\"), data.getInventory());\n//        }\n//\n//        return data;\n//    }\n\n//    @Override\n//    public void writeBlockEntity(BlockPosition position, Data blockData, NBTCompound nbt) {\n//        // TODO: CustomName\n//        // TODO: Lock\n//        // TODO: LootTable\n//        // TODO: LootTableSeed\n//        if (blockData instanceof ChestBlockEntity) {\n//            ChestBlockEntity data = (ChestBlockEntity) blockData;\n//            NBTList<NBTCompound> list = new NBTList<>(NBTTypes.TAG_Compound);\n//            NBTUtils.saveAllItems(list, data.getInventory());\n//            nbt.set(\"Items\", list);\n//        }\n//    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/JukeboxBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.jukebox.JukeboxSong;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.server.worldevent.WorldEvent;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\nimport java.util.Random;\n\n/**\n * Reimplementation of the jukebox block\n * <p>\n * Requires onPlace enhancements\n */\npublic class JukeboxBlockBehaviour extends VanillaBlockBehaviour {\n\n    public static final Tag<ItemStack> DISC_KEY = Tag.ItemStack(\"minestom:jukebox_disc\");\n\n    public JukeboxBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n    @Override\n    public void onDestroy(@NotNull Destroy destroy) {\n        if (!(destroy instanceof PlayerDestroy)) return;\n        stopPlayback(destroy.getInstance(), destroy.getBlockPosition(), destroy.getBlock());\n    }\n\n    public @Nullable ItemStack getDisc(Block block) {\n        return block.getTag(DISC_KEY);\n    }\n\n    public @NotNull Block withDisc(Block block, @NotNull ItemStack disc) {\n        if (isNotMusicDisc(disc)) {\n            throw new IllegalArgumentException(\"disc passed to JukeboxBlockHandle#withDisc was not a music disc.\");\n        }\n        return block.withTag(DISC_KEY, disc);\n    }\n\n    private boolean isNotMusicDisc(ItemStack itemStack) {\n        return !itemStack.has(DataComponents.JUKEBOX_PLAYABLE);\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        Player player = interaction.getPlayer();\n        PlayerHand hand = interaction.getHand();\n        Instance instance = interaction.getInstance();\n        Block block = interaction.getBlock();\n        Point pos = interaction.getBlockPosition();\n        ItemStack heldItem = player.getItemInMainHand();\n\n        ItemStack stack = this.getDisc(block);\n\n        if (stack != null && !stack.isAir()) {\n            stopPlayback(instance, pos, block);\n            block = block.withTag(DISC_KEY, ItemStack.AIR);\n            instance.setBlock(pos, block.withProperty(\"has_record\", \"false\"));\n            return true;\n        }\n\n        if (isNotMusicDisc(heldItem)) {\n            return true;\n        }\n\n        instance.setBlock(pos, withDisc(block, heldItem).withProperty(\"has_record\", \"true\"));\n\n        InventoryManipulation.consumeItemIfNotCreative(player, heldItem, hand);\n\n        JukeboxSong song = heldItem.get(DataComponents.JUKEBOX_PLAYABLE).holder().resolve(MinecraftServer.getJukeboxSongRegistry());\n        DynamicRegistry.Key<JukeboxSong> songKey = MinecraftServer.getJukeboxSongRegistry().getKey(song);\n        int songId = MinecraftServer.getJukeboxSongRegistry().getId(songKey);\n\n        // TODO: Group packet?\n        instance.getPlayers()\n                .stream()\n                .filter(player1 -> player1.getDistance(pos) < 64)\n                .forEach(player1 ->\n                        player1.playEffect(\n                                WorldEvent.SOUND_PLAY_JUKEBOX_SONG,\n                                pos.blockX(),\n                                pos.blockY(),\n                                pos.blockZ(),\n                                songId,\n                                false\n                        )\n                );\n\n        return true;\n    }\n\n    @Override\n    public boolean isTickable() {\n        return true;\n    }\n\n    public void tick(@NotNull Tick tick) {\n        Instance instance = tick.getInstance();\n\n        long age = instance.getWorldAge();\n\n        // Continue only every 3 seconds\n        if (age % (MinecraftServer.TICK_PER_SECOND * 3L) != 0) {\n        }\n\n        // TODO: Play sound to all players without the sound playing\n    }\n\n//    @Override\n//    public Data readBlockEntity(NBTCompound nbt, Instance instance, BlockPosition position, Data originalData) {\n//        JukeboxBlockEntity data;\n//        if (originalData instanceof JukeboxBlockEntity) {\n//            data = (JukeboxBlockEntity) originalData;\n//        } else {\n//            data = new JukeboxBlockEntity(position.copy());\n//        }\n//\n//        if(nbt.containsKey(\"RecordItem\")) {\n//            data.setDisc(ItemStack.fromNBT(nbt.getCompound(\"RecordItem\")));\n//        }\n//        return super.readBlockEntity(nbt, instance, position, originalData);\n//    }\n//\n//    @Override\n//    public void writeBlockEntity(BlockPosition position, Data blockData, NBTCompound nbt) {\n//        if(blockData instanceof JukeboxBlockEntity) {\n//            JukeboxBlockEntity data = (JukeboxBlockEntity) blockData;\n//            nbt.set(\"RecordItem\", data.getDisc().toNBT());\n//        }\n//    }\n\n    /**\n     * Stops playback in an instance\n     */\n    private void stopPlayback(Instance instance, Point pos, Block block) {\n        ItemEntity discEntity = new ItemEntity(Objects.requireNonNull(getDisc(block)));\n        discEntity.setInstance(instance);\n        discEntity.teleport(new Pos(pos.x() + 0.5f, pos.y() + 1f, pos.z() + 0.5f));\n        discEntity.setPickable(true);\n\n        Random rng = new Random();\n        final float horizontalSpeed = 2f;\n        final float verticalSpeed = 5f;\n\n        discEntity.setVelocity(new Vec(\n                rng.nextGaussian() * horizontalSpeed,\n                rng.nextFloat() * verticalSpeed,\n                rng.nextGaussian() * horizontalSpeed\n        ));\n\n        discEntity.setInstance(instance);\n\n        // TODO: Group Packet?\n        instance.getPlayers().forEach(playerInInstance -> {\n            // stop playback\n            playerInInstance.playEffect(\n                    WorldEvent.SOUND_STOP_JUKEBOX_SONG,\n                    pos.blockX(),\n                    pos.blockY(),\n                    pos.blockZ(),\n                    -1,\n                    false\n            );\n        });\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/NetherPortalBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdatable;\nimport net.minestom.vanilla.blockupdatesystem.BlockUpdateInfo;\nimport net.minestom.vanilla.dimensions.VanillaDimensionTypes;\nimport net.minestom.vanilla.system.NetherPortal;\nimport net.minestom.vanilla.system.nether.EntityEnterNetherPortalEvent;\nimport net.minestom.vanilla.system.nether.NetherPortalTeleportEvent;\nimport net.minestom.vanilla.system.nether.NetherPortalUpdateEvent;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Optional;\n\npublic class NetherPortalBlockBehaviour extends VanillaBlockBehaviour implements BlockUpdatable {\n\n    /**\n     * Time the entity has spent inside a portal. Reset when entering a different portal or by\n     * reentering a portal after leaving one\n     */\n    public static final Tag<Long> TICKS_SPENT_IN_PORTAL_KEY = Tag.Long(\"minestom:time_spent_in_nether_portal\").defaultValue(0L);\n\n    /**\n     * Prevents multiple updates from different portal blocks\n     */\n    public static final Tag<Long> LAST_PORTAL_UPDATE_KEY = Tag.Long(\"minestom:last_nether_portal_update_time\").defaultValue(Long.MAX_VALUE);\n\n    /**\n     * Used to check whether the last portal entered is corresponding to this portal block or not\n     */\n    public static final Tag<Long> LAST_PORTAL_KEY = Tag.Long(\"minestom:last_nether_portal\");\n\n    /**\n     * Time before teleporting an entity\n     */\n    public static final Tag<Long> PORTAL_COOLDOWN_TIME_KEY = Tag.Long(\"minestom:nether_portal_cooldown_time\").defaultValue(0L);\n\n    /**\n     * The portal related to this block\n     */\n    public static final Tag<Long> RELATED_PORTAL_KEY = Tag.Long(\"minestom:related_portal\");\n\n    public NetherPortalBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n    @Override\n    public void onTouch(@NotNull Touch touch) {\n        Block block = touch.getBlock();\n        Instance instance = touch.getInstance();\n        Point pos = touch.getBlockPosition();\n        Entity touching = touch.getTouching();\n\n        Long lastPortalUpdate = block.getTag(LAST_PORTAL_UPDATE_KEY);\n\n        if (lastPortalUpdate == null) {\n            return;\n        }\n\n        if (lastPortalUpdate < touching.getAliveTicks() - 2) { // if a tick happened with no portal update, that means the entity left the portal at some point\n            Block newBlock = block\n                    .withTag(LAST_PORTAL_UPDATE_KEY, 0L)\n                    .withTag(TICKS_SPENT_IN_PORTAL_KEY, 0L);\n\n            instance.setBlock(pos, newBlock);\n            return;\n        }\n\n        if (lastPortalUpdate == touching.getAliveTicks()) {\n            return;\n        }\n\n        NetherPortal portal = getPortal(block);\n        long ticksSpentInPortal = updateTimeInPortal(instance, pos, touching, block, portal);\n\n        Long portalCooldownTime = block.getTag(PORTAL_COOLDOWN_TIME_KEY);\n\n        if (portalCooldownTime == null) {\n            portalCooldownTime = 0L;\n        }\n\n        if (ticksSpentInPortal >= portalCooldownTime) {\n            attemptTeleport(instance, touching, block, ticksSpentInPortal, portal);\n        }\n    }\n\n    private long updateTimeInPortal(Instance instance, Point position, Entity touching, Block block, NetherPortal portal) {\n        Block newBlock = block;\n\n        newBlock = newBlock.withTag(LAST_PORTAL_UPDATE_KEY, touching.getAliveTicks());\n        Long ticksSpentInPortal = block.getTag(TICKS_SPENT_IN_PORTAL_KEY);\n\n        if (ticksSpentInPortal == null) {\n            ticksSpentInPortal = 0L;\n        }\n\n        NetherPortal portalEntityWasIn = NetherPortal.fromId(newBlock.getTag(LAST_PORTAL_KEY));\n\n        if (portal != portalEntityWasIn) {\n            ticksSpentInPortal = 0L; // reset counter\n        }\n\n        newBlock = newBlock.withTag(LAST_PORTAL_KEY, portal.id()); // data.set(, portal, NetherPortal.class);\n\n        if (ticksSpentInPortal == 0) {\n            Event event = new EntityEnterNetherPortalEvent(touching, position, portal);\n\n            MinecraftServer.getGlobalEventHandler().call(event);\n        }\n\n        ticksSpentInPortal++;\n\n        newBlock = newBlock.withTag(TICKS_SPENT_IN_PORTAL_KEY, ticksSpentInPortal);\n\n        instance.setBlock(position, newBlock);\n\n        Event event = new NetherPortalUpdateEvent(touching, position, portal, instance, ticksSpentInPortal);\n\n        MinecraftServer.getGlobalEventHandler().call(event);\n        return ticksSpentInPortal;\n    }\n\n    private void attemptTeleport(Instance instance, Entity touching, Block block, long ticksSpentInPortal, NetherPortal portal) {\n        DimensionType targetDimension;\n        Point position = touching.getPosition();\n\n        double targetX = position.x() / 8;\n        double targetY = position.y();\n        double targetZ = position.z() / 8;\n\n        var key = instance.getDimensionType();\n        DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(key);\n        if (dimension.effects().equals(\"nether\")) {\n            targetDimension = MinecraftServer.getDimensionTypeRegistry().get(DimensionType.OVERWORLD);\n            targetX = position.x() * 8;\n            targetZ = position.z() * 8;\n        } else {\n            targetDimension = VanillaDimensionTypes.OVERWORLD;\n        }\n\n        // TODO: event to change portal linking\n        final DimensionType finalTargetDimension = targetDimension;\n        Optional<Instance> potentialTargetInstance = MinecraftServer.getInstanceManager().getInstances().stream()\n                .filter(in -> {\n                    var key1 = in.getDimensionType();\n                    return MinecraftServer.getDimensionTypeRegistry().get(key1) == targetDimension;\n                })\n                .findFirst();\n\n        if (potentialTargetInstance.isEmpty()) {\n            return;\n        }\n\n        Instance targetInstance = potentialTargetInstance.get();\n        Pos targetPosition = new Pos(targetX, targetY, targetZ);\n        NetherPortal targetPortal = getCorrespondingNetherPortal(targetInstance, targetPosition);\n\n        boolean generatePortal = false;\n        if (targetPortal == null) { // no existing portal, will create one\n\n            NetherPortal.Axis axis = portal.getAxis();\n            Pos bottomRight = new Pos(\n                    targetX - axis.xMultiplier,\n                    targetY - 1,\n                    targetZ - axis.zMultiplier\n            );\n\n            Pos topLeft = new Pos(\n                    targetX + 2 * axis.xMultiplier,\n                    targetY + 3,\n                    targetZ + 2 * axis.zMultiplier\n            );\n\n            targetPortal = new NetherPortal(portal.getAxis(), bottomRight, topLeft);\n            generatePortal = true;\n        }\n\n        targetPosition = calculateTargetPosition(touching, portal, targetPortal);\n\n        NetherPortalTeleportEvent event = new NetherPortalTeleportEvent(touching, position, portal, ticksSpentInPortal, targetInstance, targetPosition, targetPortal, generatePortal);\n        MinecraftServer.getGlobalEventHandler().call(event);\n\n        if (!event.isCancelled()) {\n            Block newBlock = block\n                    .withTag(LAST_PORTAL_UPDATE_KEY, 0L)\n                    .withTag(LAST_PORTAL_KEY, portal.id())\n                    .withTag(TICKS_SPENT_IN_PORTAL_KEY, 0L);\n            instance.setBlock(position, newBlock);\n            teleport(instance, touching, event);\n        }\n    }\n\n    private @Nullable NetherPortal getCorrespondingNetherPortal(Instance targetInstance, Point targetPosition) {\n        Block block = targetInstance.getBlock(targetPosition);\n        return NetherPortal.fromId(block.getTag(RELATED_PORTAL_KEY));\n    }\n\n    private Pos calculateTargetPosition(Entity touching, NetherPortal portal, NetherPortal targetPortal) {\n        Point targetCenter = targetPortal.getCenter();\n\n        if (portal == null) { // if this block is not isolated\n            return new Pos(\n                    targetCenter.x() + 0.5,\n                    targetCenter.y(),\n                    targetCenter.z() + 0.5\n            );\n        }\n\n        Pos touchingPos = touching.getPosition();\n        double touchingX = touchingPos.x();\n        double touchingY = touchingPos.y();\n        double touchingZ = touchingPos.z();\n\n        Vec portalCenter = portal.getCenter();\n        double portalCenterX = portalCenter.x();\n        double portalCenterY = portalCenter.y();\n        double portalCenterZ = portalCenter.z();\n\n        NetherPortal.Axis portalAxis = portal.getAxis();\n        double portalAxisXMultiplier = portalAxis.xMultiplier;\n        double portalAxisZMultiplier = portalAxis.zMultiplier;\n\n        NetherPortal.Axis targetAxis = targetPortal.getAxis();\n        double targetAxisXMultiplier = targetAxis.xMultiplier;\n        double targetAxisZMultiplier = targetAxis.zMultiplier;\n\n        double relativeX = (touchingX - portalCenterX) / (portal.computeWidth() * portalAxisXMultiplier + portalAxisZMultiplier);\n        double relativeY = (touchingY - portalCenterY) / portal.computeHeight();\n        double relativeZ = (touchingZ - portalCenterZ) / (portal.computeWidth() * portalAxisZMultiplier + portalAxisXMultiplier);\n\n        double targetMultiplierX = (targetPortal.computeWidth() * targetAxisXMultiplier + targetAxisZMultiplier);\n        double targetMultiplierY = targetPortal.computeHeight();\n        double targetMultiplierZ = (targetPortal.computeWidth() * targetAxisZMultiplier + targetAxisXMultiplier);\n\n        return new Pos(\n                targetCenter.x() + relativeX * targetMultiplierX,\n                targetCenter.y() + relativeY * targetMultiplierY,\n                targetCenter.z() + relativeZ * targetMultiplierZ\n        );\n    }\n\n    private void teleport(Instance instance, Entity touching, NetherPortalTeleportEvent event) {\n        Instance targetInstance = event.getTargetInstance();\n        if (event.createsNewPortal()) {\n            event.getTargetPortal().generate(targetInstance);\n        }\n\n        if (targetInstance != instance) {\n            touching.setInstance(targetInstance);\n        }\n\n        Pos targetTeleportationPosition = new Pos(event.getTargetPosition());\n\n        touching.teleport(targetTeleportationPosition).thenRun(() -> {\n            Vec velocity = touching.getVelocity();\n\n            if (\n                    event.getPortal() != null &&\n                            event.getPortal().getAxis()\n                                    != event.getTargetPortal().getAxis()\n            ) {\n                double swapTmp = velocity.x();\n\n                touching.setVelocity(new Vec(\n                        swapTmp,\n                        velocity.z(),\n                        swapTmp\n                ));\n            }\n        });\n    }\n\n    @Override\n    public void onDestroy(@NotNull Destroy destroy) {\n        Block block = destroy.getBlock();\n        Instance instance = destroy.getInstance();\n\n        NetherPortal netherPortal = getPortal(block);\n        if (netherPortal != null) {\n            netherPortal.breakFrame(instance);\n            netherPortal.unregister(instance);\n        }\n    }\n\n    private NetherPortal getPortal(Block block) {\n        return NetherPortal.fromId(block.getTag(RELATED_PORTAL_KEY));\n    }\n\n//    @Override\n//    public void updateFromNeighbor(Instance instance, Point thisPosition, Point neighborPosition, boolean directNeighbor) {\n//\n//    }\n\n    private void breakPortalIfNoLongerValid(Instance instance, Point blockPosition) {\n        NetherPortal netherPortal = getPortal(instance.getBlock(blockPosition));\n\n        if (netherPortal == null) {\n            return;\n        }\n\n        if (netherPortal.isStillValid(instance)) {\n            return;\n        }\n\n        netherPortal.breakFrame(instance);\n    }\n\n    public void setRelatedPortal(Instance instance, Point blockPosition, Block block, NetherPortal portal) {\n        instance.setBlock(blockPosition, block.withTag(RELATED_PORTAL_KEY, portal.id()));\n    }\n\n    @Override\n    public void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info) {\n        breakPortalIfNoLongerValid(instance, pos);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TNTBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.VanillaRegistry;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.entitymeta.EntityTags;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Random;\n\npublic class TNTBlockBehaviour extends VanillaBlockBehaviour {\n\n    public static final Random TNT_RANDOM = new Random();\n\n    public TNTBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context);\n    }\n\n//    @Override\n//    protected BlockPropertyList createPropertyValues() {\n//        return new BlockPropertyList().booleanProperty(\"unstable\");\n//    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        Point blockPosition = interaction.getBlockPosition();\n        Player player = interaction.getPlayer();\n        PlayerHand hand = interaction.getHand();\n\n        if (player.getItemInHand(hand).material() != Material.FLINT_AND_STEEL) {\n            return true;\n        }\n\n        player.getInstance().setBlock(blockPosition, Block.AIR);\n        spawnPrimedTNT(player.getInstance(), blockPosition, 80);\n\n        return true;\n    }\n\n    private void spawnPrimedTNT(Instance instance, Point blockPosition, int fuseTime) {\n        Pos initialPosition = new Pos(blockPosition.blockX() + 0.5f, blockPosition.blockY() + 0f, blockPosition.blockZ() + 0.5f);\n\n        // Create the entity\n        VanillaRegistry.EntityContext entityContext = context.vri().entityContext(EntityType.TNT, initialPosition,\n                writable -> writable.setTag(EntityTags.PrimedTnt.FUSE_TIME, fuseTime));\n        Entity primedTnt = context.vri().createEntityOrDummy(entityContext);\n\n        // Spawn it with random velocity\n        primedTnt.setInstance(instance, initialPosition);\n        primedTnt.setVelocity(new Vec(TNT_RANDOM.nextFloat() * 2f - 1f, TNT_RANDOM.nextFloat() * 5f, TNT_RANDOM.nextFloat() * 2f - 1f));\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TrappedChestBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic class TrappedChestBlockBehaviour extends InventoryBlockBehaviour {\n    // TODO: redstone signal\n\n    public TrappedChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) {\n        super(context, InventoryType.CHEST_3_ROW, Component.text(\"Trapped Chest\"));\n    }\n\n//    @Override\n//    protected BlockPropertyList createPropertyValues() {\n//        return super.createPropertyValues().property(\"type\", \"single\", \"left\", \"right\");\n//    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockInventory.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.chestlike;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class BlockInventory extends Inventory {\n    private static final Map<Point, BlockInventory> BLOCK_INVENTORY_MAP = new ConcurrentHashMap<>();\n\n    protected final Instance instance;\n    protected final Point pos;\n\n    private BlockInventory(Instance instance, Point pos, InventoryType inventoryType, Component title) {\n        super(inventoryType, title);\n\n        this.instance = instance;\n        this.pos = pos;\n\n        // Set items\n        List<ItemStack> itemsList = instance.getBlock(pos).getTag(InventoryBlockBehaviour.TAG_ITEMS);\n        if (itemsList != null) {\n            for (int i = 0; i < itemsList.size(); i++) {\n                this.itemStacks[i] = itemsList.get(i);\n            }\n        }\n    }\n\n    public static BlockInventory from(Instance instance, Point pos, InventoryType inventoryType, Component title) {\n        BlockInventory inv = BLOCK_INVENTORY_MAP.get(pos);\n        if (inv == null) {\n            inv = new BlockInventory(instance, pos, inventoryType, title);\n            BLOCK_INVENTORY_MAP.put(pos, inv);\n        }\n        if (inv.getInventoryType() != inventoryType) {\n            throw new IllegalStateException(\"Inventory type mismatch\");\n        }\n        if (!inv.getTitle().equals(title)) {\n            throw new IllegalStateException(\"Inventory title mismatch\");\n        }\n        return inv;\n    }\n\n    public static @NotNull List<ItemStack> remove(Instance instance, Point pos) {\n        BlockInventory inv = BLOCK_INVENTORY_MAP.get(pos);\n        if (inv == null) {\n            return List.of();\n        }\n        BLOCK_INVENTORY_MAP.remove(pos);\n        return List.of(inv.itemStacks);\n    }\n\n    @Override\n    public void setItemStack(int slot, @NotNull ItemStack itemStack) {\n        super.setItemStack(slot, itemStack);\n        instance.setBlock(pos, instance.getBlock(pos).withTag(InventoryBlockBehaviour.TAG_ITEMS, List.of(itemStacks)));\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockItems.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.chestlike;\n\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.vanilla.tag.Tags;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.UnmodifiableView;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * The key difference between BlockItems and BlockInventory is that BlockItems\n * does not require specifying an inventoryType or title. It is designed for simpler\n * container block management.\n */\npublic class BlockItems {\n\n    private static final Tag<List<ItemStack>> FALLBACK_TAG_ITEMS = Tag.ItemStack(\"vri:container_items\")\n            .list();\n\n    private static Map<String, Tag<List<ItemStack>>> TAG_ITEMS_BY_BLOCK;\n\n    private static Tag<List<ItemStack>> getTagForBlock(Block block) {\n        return TAG_ITEMS_BY_BLOCK.getOrDefault(block.name(), FALLBACK_TAG_ITEMS).defaultValue(List.of());\n    }\n\n    private final List<ItemStack> items;\n\n    private BlockItems(List<ItemStack> items) {\n        this.items = new ArrayList<>(items);\n    }\n\n    public static BlockItems from(Block block) {\n        return new BlockItems(block.getTag(getTagForBlock(block)));\n    }\n\n    public static BlockItems from(Block block, int requireStacks) {\n        BlockItems items = from(block);\n        if (items.size() != requireStacks) {\n            items.requireStacks(requireStacks);\n        }\n        return items;\n    }\n\n    private void requireStacks(int requireStacks) {\n        // remove from the top, or add to the top.\n        if (items.size() < requireStacks) {\n            items.addAll(Collections.nCopies(requireStacks - items.size(), ItemStack.AIR));\n        } else if (items.size() > requireStacks) {\n            items.subList(requireStacks, items.size()).clear();\n        }\n    }\n\n    public @UnmodifiableView @NotNull List<ItemStack> itemStacks() {\n        return Collections.unmodifiableList(items);\n    }\n\n    public int size() {\n        return items.size();\n    }\n\n    public boolean isEmpty() {\n        return items.isEmpty();\n    }\n\n    public boolean isAir() {\n        return items.isEmpty() || items.stream().allMatch(ItemStack::isAir);\n    }\n\n    public Block apply(Block block) {\n        return block.withTag(getTagForBlock(block), items);\n    }\n\n    public void setItems(List<ItemStack> itemStacks) {\n        items.clear();\n        items.addAll(itemStacks);\n    }\n\n    public ItemStack get(int index) {\n        return items.get(index);\n    }\n\n    public void set(int index, ItemStack item) {\n        items.set(index, item);\n    }\n\n    static {\n        Map<Block, Tag<List<ItemStack>>> tagItemsByBlock = new HashMap<>();\n\n        // some blocks need to be represented by a specific nbt tag to be visible to the client.\n        tagItemsByBlock.put(Block.CAMPFIRE, Tags.Blocks.Campfire.ITEMS);\n\n        BlockItems.TAG_ITEMS_BY_BLOCK = Map.copyOf(tagItemsByBlock.entrySet().stream()\n                .map(entry -> Map.entry(entry.getKey().name(), entry.getValue()))\n                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)));\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/DoubleChestInventory.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.chestlike;\n\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Stream;\n\npublic class DoubleChestInventory extends Inventory {\n    private final BlockInventory left;\n    private final BlockInventory right;\n\n    public DoubleChestInventory(BlockInventory left, BlockInventory right, String title) {\n        super(InventoryType.CHEST_6_ROW, title);\n        this.left = left;\n        this.right = right;\n    }\n\n    @Override\n    public @NotNull ItemStack getItemStack(int slot) {\n        if (slot < left.getSize()) {\n            return left.getItemStack(slot);\n        }\n        return right.getItemStack(slot - left.getSize());\n    }\n\n    @Override\n    public void setItemStack(int slot, @NotNull ItemStack itemStack) {\n        if (slot < left.getSize()) {\n            left.setItemStack(slot, itemStack);\n        } else {\n            right.setItemStack(slot - left.getSize(), itemStack);\n        }\n    }\n\n    @Override\n    @Deprecated\n    public ItemStack[] getItemStacks() {\n        return itemStacks().toArray(ItemStack[]::new);\n    }\n\n    public List<ItemStack> itemStacks() {\n        return Stream.of(left, right)\n                .map(BlockInventory::getItemStacks)\n                .map(List::of)\n                .flatMap(Collection::stream)\n                .toList();\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatableBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.oxidisable;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\nimport net.minestom.vanilla.randomticksystem.RandomTickable;\nimport net.minestom.vanilla.utils.MathUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Random;\n\n/**\n * Oxidation (<a href=\"https://minecraft.fandom.com/wiki/Block_of_Copper#Waxing\">Source</a>)\n * <p>\n *     Non-waxed copper blocks have four stages of oxidation (including the initial normal state). Lightning bolts and\n *     axes can remove the oxidation on copper blocks. As the block begins to oxidize (exposed copper), it gets\n *     discolored and green spots begin to appear. As the oxidation continues (weathered copper), the block is a green\n *     color with brown spots. In the last stage (oxidized copper), the block is teal with several green spots.\n *     Oxidation of copper blocks relies only on random ticks. Rain or water does not accelerate oxidation, and covering\n *     copper blocks with other blocks does not prevent oxidation. In Java Edition, groups of non-waxed copper blocks\n *     oxidize far more slowly than single copper blocks that are spaced at least 4 blocks apart. This is because a\n *     block in a group being less oxidized than the others slows down the oxidation process for all other blocks within\n *     4 blocks of taxicab distance. However, if one wishes to increase the oxidation speed, placing oxidized copper\n *     blocks around less oxidized copper blocks does not offer a speed improvement over simply placing the blocks 4\n *     apart. The calculations for the oxidation behavior are as follows:\n *     In Java Edition, when a random tick is given, a copper block has a 64/1125 chance to enter a state called\n *     pre-oxidation. This means a copper block enters pre-oxidation after approximately 20 minutes.\n *     In pre-oxidation, the copper block searches its nearby non-waxed copper blocks for a distance of 4 blocks taxicab\n *     distance. If there is any copper block that has a lower oxidation level, then the pre-oxidation ends, meaning\n *     that this copper block does not weather. Let a be the number of all nearby non-waxed copper blocks, and b be the\n *     number of nearby non-waxed copper blocks that have a higher oxidation level. We derive the value of c from this\n *     equation: c = b + 1/a + 1. We also let the modifying factor m be 0.75 if the copper block has no oxidation level,\n *     or 1 if the copper block is exposed or weathered.[1] Then the oxidation probability is mc2. For example, an\n *     unweathered copper block surrounded by 6 unweathered copper blocks and 6 exposed copper blocks has a 21.7% chance\n *     to oxidize if it enters the pre-oxidation state. In this case, a = 12, b = 6, and m = 0.75.[2] The most efficient\n *     way of laying out the copper blocks for oxidation is in a 7×7×6 face-centered cubic (fcc)/cubic close-packed\n *     (ccp) lattice.\n * <p/>\n */\npublic class OxidatableBlockBehaviour extends WaxableBlockBehaviour implements RandomTickable, OxygenSensitive {\n\n    private final short previous;\n    private final short oxidised;\n    private final int oxidisedLevel;\n\n\n    public OxidatableBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block previous, Block oxidised, Block waxed, int oxidisedLevel) {\n        super(context, waxed);\n        this.previous = (short) previous.stateId();\n        this.oxidised = (short) oxidised.stateId();\n        this.oxidisedLevel = oxidisedLevel;\n    }\n\n    @Override\n    public void randomTick(@NotNull RandomTick randomTick) {\n        // Exit now if the block cannot be oxidised anymore\n        if (oxidised == context.stateId()) return;\n\n        Random random = context.vri().random(randomTick.instance());\n        // In Java Edition, when a random tick is given, a copper block has a 64/1125 chance to enter a state called pre-oxidation.\n        // This means a copper block enters pre-oxidation after approximately 20 minutes.\n        if (random.nextInt(1125) >= 64) {\n            return;\n        }\n\n        // In pre-oxidation, the copper block searches its nearby non-waxed copper blocks for a distance of 4 blocks\n        // taxicab distance. If there is any copper block that has a lower oxidation level, then the pre-oxidation ends,\n        // meaning that this copper block does not weather.\n        List<Block> nearbyBlocks = MathUtils.getWithinManhattanDistance(randomTick.position(), 4)\n                .stream()\n                .map(point -> randomTick.instance().isChunkLoaded(point) ?\n                        randomTick.instance().getBlock(point) : Block.AIR)\n                .toList();\n        int minOxidisedAround = nearbyBlocks.stream()\n                .filter(block -> block.handler() instanceof OxygenSensitive)\n                .map(block -> (OxygenSensitive) block.handler())\n                .mapToInt(OxygenSensitive::oxidisedLevel)\n                .min()\n                .orElse(Integer.MAX_VALUE);\n\n        if (minOxidisedAround < oxidisedLevel) {\n            return;\n        }\n\n        // Let a be the number of all nearby non-waxed copper blocks, and b be the number of nearby non-waxed copper\n        // blocks that have a higher oxidation level. We derive the value of c from this equation: c = b + 1/a + 1.\n        // We also let the modifying factor m be 0.75 if the copper block has no oxidation level, or 1 if the copper\n        // block is exposed or weathered.[1] Then the oxidation probability is mc2.\n        // For example, an unweathered copper block surrounded by 6 unweathered copper blocks and 6 exposed copper\n        // blocks has a 21.7% chance to oxidize if it enters the pre-oxidation state. In this case, a = 12, b = 6, and\n        // m = 0.75.[2]\n        double a = (int) nearbyBlocks.stream()\n                .filter(block -> block.handler() instanceof OxygenSensitive os\n                        && os.oxidisedLevel() == oxidisedLevel()) // Filter out unrelated blocks\n                .count();\n        double b = (int) nearbyBlocks.stream()\n                .filter(block -> !(block.handler() instanceof WaxedBlockBehaviour))\n                .filter(block -> block.handler() instanceof OxygenSensitive os\n                        && os.oxidisedLevel() > oxidisedLevel()) // Filter out unrelated blocks\n                .map(block -> (OxygenSensitive) block.handler()).filter(Objects::nonNull)\n                .filter(handler -> handler.oxidisedLevel() > oxidisedLevel)\n                .count();\n        double m = oxidisedLevel == 0 ? 0.75 : 1;\n        double c = (b + 1) / (a + 1);\n        double probability = m * c * c;\n\n        if (random.nextDouble() < probability) {\n            Block block = Block.fromStateId(oxidised);\n            Objects.requireNonNull(block, \"Block with state id \" + oxidised + \" was not found\");\n            randomTick.instance().setBlock(randomTick.position(), block);\n        }\n    }\n\n    @Override\n    public int oxidisedLevel() {\n        return oxidisedLevel;\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        PlayerHand hand = interaction.getHand();\n        Player player = interaction.getPlayer();\n        Block interactionBlock = interaction.getBlock();\n\n        ItemStack item = player.getItemInHand(hand);\n        Material material = item.material();\n\n        if (interactionBlock.stateId() != previous\n                && material.key().value().toLowerCase().contains(\"_axe\")) { // TODO: Better way to check if it's an axe\n            Block previousBlock = Block.fromStateId(previous);\n            Objects.requireNonNull(previousBlock, \"Block with state id \" + previous + \" was not found\");\n            interaction.getInstance().setBlock(interaction.getBlockPosition(), previousBlock);\n            InventoryManipulation.damageItemIfNotCreative(player, hand, 1);\n            return false;\n        }\n        return super.onInteract(interaction);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatedBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.oxidisable;\n\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic abstract class OxidatedBlockBehaviour extends WaxableBlockBehaviour implements OxygenSensitive {\n\n    private final int oxidisedLevel;\n\n    public OxidatedBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block waxed, int oxidisedLevel) {\n        super(context, waxed);\n        this.oxidisedLevel = oxidisedLevel;\n    }\n\n    @Override\n    public int oxidisedLevel() {\n        return oxidisedLevel;\n    }\n}"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxygenSensitive.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.oxidisable;\n\npublic interface OxygenSensitive {\n    int oxidisedLevel();\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxableBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.oxidisable;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\npublic abstract class WaxableBlockBehaviour extends VanillaBlockBehaviour {\n    protected final short waxedBlock;\n    protected WaxableBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block waxedTarget) {\n        super(context);\n        this.waxedBlock = (short) waxedTarget.stateId();\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        PlayerHand hand = interaction.getHand();\n        Player player = interaction.getPlayer();\n\n        ItemStack item = player.getItemInHand(hand);\n        Material material = item.material();\n\n        if (Material.HONEYCOMB.equals(material)) {\n            Block block = Block.fromStateId(waxedBlock);\n            Objects.requireNonNull(block, \"Waxed block with state id \" + waxedBlock + \" does not exist\");\n            interaction.getInstance().setBlock(interaction.getBlockPosition(), block);\n            InventoryManipulation.consumeItemIfNotCreative(player, hand, 1);\n            return false;\n        }\n        return super.onInteract(interaction);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxedBlockBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.oxidisable;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\npublic class WaxedBlockBehaviour extends OxidatedBlockBehaviour {\n    private final short unWaxed;\n\n    public WaxedBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block unWaxed, int oxidisedLevel) {\n        super(context, Block.fromStateId(context.stateId()), oxidisedLevel);\n        this.unWaxed = (short) unWaxed.stateId();\n    }\n\n    @Override\n    public boolean onInteract(@NotNull Interaction interaction) {\n        PlayerHand hand = interaction.getHand();\n        Player player = interaction.getPlayer();\n\n        ItemStack item = player.getItemInHand(hand);\n        Material material = item.material();\n\n        if (material.key().value().toLowerCase().contains(\"_axe\")) { // TODO: Better way to check if it's an axe\n            Block previousBlock = Block.fromStateId(unWaxed);\n            Objects.requireNonNull(previousBlock, \"Previous block with state id \" + unWaxed + \" was not found\");\n            interaction.getInstance().setBlock(interaction.getBlockPosition(), previousBlock);\n            InventoryManipulation.damageItemIfNotCreative(player, hand, 1);\n            return false;\n        }\n        return super.onInteract(interaction);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/BlastingFurnaceBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour;\nimport net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory;\nimport net.minestom.vanilla.events.BlastingFurnaceTickEvent;\nimport org.jetbrains.annotations.NotNull;\n\npublic class BlastingFurnaceBehaviour extends InventoryBlockBehaviour {\n    public BlastingFurnaceBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context, InventoryType.BLAST_FURNACE, Component.text(\"Blast Furnace\"));\n    }\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.BLAST_FURNACE, Component.text(\"Blast Furnace\"));\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        return false;\n    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return true;\n    }\n\n    @Override\n    public boolean isTickable() {\n        return true;\n    }\n\n    @Override\n    public void tick(@NotNull BlockHandler.Tick tick) {\n        var events = this.context.vri().process().eventHandler();\n        if (!events.hasListener(BlastingFurnaceTickEvent.class)) return; // fast exit since this is hot code\n        Instance instance = tick.getInstance();\n        Point pos = tick.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.BLAST_FURNACE, Component.text(\"Blast Furnace\"));\n        BlastingFurnaceTickEvent event = new BlastingFurnaceTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory);\n        events.call(event);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CampfireBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.recipe.*;\nimport net.minestom.server.recipe.display.RecipeDisplay;\nimport net.minestom.server.recipe.display.SlotDisplay;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blocks.behaviours.chestlike.BlockItems;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\nimport net.minestom.vanilla.tag.Tags.Blocks.Campfire;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.*;\nimport java.util.stream.IntStream;\n\npublic class CampfireBehaviour extends VanillaBlockBehaviour {\n\n    private static final int CONTAINER_SIZE = 4;\n    private static final Random RNG = new Random();\n    private final RecipeManager recipeManager;\n\n    public CampfireBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context);\n        this.recipeManager = context.vri().process().recipe();\n    }\n\n    public BlockItems getBlockItems(Block block) {\n        return BlockItems.from(block, CONTAINER_SIZE);\n    }\n\n    public @NotNull Block withCookingProgress(Block block, int slotIndex, int cookingTime) {\n        List<Integer> cookingProgress = new ArrayList<>(block.getTag(Campfire.COOKING_PROGRESS));\n        cookingProgress.set(slotIndex, cookingTime);\n        return block.withTag(Campfire.COOKING_PROGRESS, cookingProgress);\n    }\n\n    /**\n     * Appends an id to the first available slot in the campfire.\n     * @return the index of the slot the id was appended to.\n     */\n    public int appendItem(BlockItems items, @NotNull Material material) {\n        OptionalInt freeSlot = findFirstFreeSlot(items.itemStacks());\n\n        if (freeSlot.isEmpty())\n            throw new IllegalArgumentException(\"Campfire doesn't have free slot for appending an id in CampfireBehaviour#appendItem\");\n\n        ItemStack ingredient = ItemStack.of(material);\n\n        int index = freeSlot.getAsInt();\n        items.set(index, ingredient);\n\n        return index;\n    }\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Block block = interaction.getBlock();\n        Player player = interaction.getPlayer();\n        ItemStack input = player.getItemInHand(interaction.getHand());\n        Optional<Recipe> recipeOptional = findCampfireCookingRecipe(input);\n\n        if (recipeOptional.isEmpty())\n            return true;\n\n        BlockItems items = getBlockItems(block);\n        if (findFirstFreeSlot(items.itemStacks()).isEmpty())\n            return true;\n\n        boolean itemNotConsumed = !InventoryManipulation.consumeItemIfNotCreative(player, interaction.getHand(), 1);\n\n        if (itemNotConsumed)\n            return true;\n\n        Recipe recipe = recipeOptional.get();\n        if (!(recipe.createRecipeDisplays().getFirst() instanceof RecipeDisplay.Furnace furnaceRecipe)) return false;\n        Material material = getMaterialFromSlotDisplay(furnaceRecipe.ingredient());\n        if (material == null) return false;\n        int index = appendItem(items, material);\n        block = withCookingProgress(block, index, furnaceRecipe.duration());\n        instance.setBlock(pos, items.apply(block));\n        return false;\n    }\n\n    @Override\n    public void onDestroy(@NotNull BlockHandler.Destroy destroy) {\n        Instance instance = destroy.getInstance();\n        Point pos = destroy.getBlockPosition();\n        Block block = destroy.getBlock();\n\n        // TODO: Introduce a way to get the block this is getting replaced with, enabling us to remove the tick delay.\n        instance.scheduleNextTick(ignored -> {\n            Block newBlock = instance.getBlock(pos);\n            if (newBlock.compare(block)) {\n                // Same block, don't remove campfire\n                return;\n            }\n\n            // Different block, remove campfire\n            List<ItemStack> items = BlockItems.from(newBlock).itemStacks();\n\n            for (ItemStack item : items) {\n\n                if (item == null) {\n                    continue;\n                }\n\n                dropItem(instance, pos, item);\n            }\n        });\n    }\n\n    @Override\n    public void tick(@NotNull BlockHandler.Tick tick) {\n        Instance instance = tick.getInstance();\n        Block block = tick.getBlock();\n        Point pos = tick.getBlockPosition();\n        BlockItems items = getBlockItems(block);\n\n        if (items.isAir())\n            return;\n\n        List<Integer> cookingProgress = new ArrayList<>(block.getTag(Campfire.COOKING_PROGRESS));\n\n        boolean lit = Boolean.parseBoolean(block.getProperty(\"lit\"));\n        if (!lit) {\n            for (ItemStack item : items.itemStacks())\n                dropItem(instance, pos, item);\n            items.setItems(Collections.nCopies(4, ItemStack.AIR));\n            instance.setBlock(pos, items.apply(block));\n            return;\n        }\n\n        for (ListIterator<Integer> i = cookingProgress.listIterator(); i.hasNext(); ) {\n            int index = i.nextIndex();\n            Integer progress = i.next();\n            Material inputMaterial = items.get(index).material();\n\n            if (Material.AIR.equals(inputMaterial))\n                continue;\n\n            if (progress <= 0) {\n                endCampfireCookingProgress(tick.getInstance(), tick.getBlockPosition(), ItemStack.of(inputMaterial));\n                items.set(index, ItemStack.AIR);\n                continue;\n            }\n\n            progress -= 1;\n            i.set(progress);\n        }\n\n        block = items.apply(block);\n        block = block.withTag(Campfire.COOKING_PROGRESS, cookingProgress);\n        instance.setBlock(pos, block);\n    }\n\n    @Override\n    public @NotNull Collection<Tag<?>> getBlockEntityTags() {\n        return List.of(Campfire.ITEMS);\n    }\n\n    @Override\n    public boolean isTickable() {\n        return true;\n    }\n\n    private void endCampfireCookingProgress(Instance instance, Point pos, ItemStack input) {\n        Optional<Recipe> recipeOptional = findCampfireCookingRecipe(input);\n        if (recipeOptional.isEmpty())\n            throw new IllegalArgumentException(\"Cannot end campfire cooking progress because input recipe doesn't found\");\n        Material material = getRecipeResult(recipeOptional.get());\n        if (material == null) {\n            return;\n        }\n        dropItem(instance, pos, ItemStack.of(material));\n    }\n\n    private void dropItem(Instance instance, Point pos, ItemStack item) {\n        ItemEntity resultItemEntity = new ItemEntity(item);\n        resultItemEntity.setInstance(instance);\n        resultItemEntity.teleport(new Pos(pos.x() + RNG.nextDouble(), pos.y() + .5f, pos.z() + RNG.nextDouble()));\n    }\n\n    private OptionalInt findFirstFreeSlot(List<ItemStack> items) {\n        return IntStream.range(0, items.size()).filter(index -> items.get(index).isAir()).findFirst();\n    }\n\n    private Material getRecipeResult(Recipe recipe) {\n        RecipeDisplay d = recipe.createRecipeDisplays().getFirst();\n        if (d instanceof RecipeDisplay.Furnace f) {\n            return getMaterialFromSlotDisplay(f.result());\n        }\n        return null;\n    }\n\n    private Material getRecipeInput(Recipe recipe) {\n        RecipeDisplay d = recipe.createRecipeDisplays().getFirst();\n        if (d instanceof RecipeDisplay.Furnace f) {\n            return getMaterialFromSlotDisplay(f.ingredient());\n        }\n        return null;\n    }\n\n    private Material getMaterialFromSlotDisplay(SlotDisplay slotDisplay) {\n        return switch (slotDisplay) {\n            case SlotDisplay.Item i -> i.material();\n            case SlotDisplay.ItemStack i -> i.itemStack().material();\n            default -> null;\n        };\n    }\n\n    private Optional<Recipe> findCampfireCookingRecipe(ItemStack input) {\n        if (input == null)\n            return Optional.empty();\n        return recipeManager\n                .getRecipes().stream()\n                .filter(r -> r.recipeBookCategory() == RecipeBookCategory.CAMPFIRE)\n                .filter(recipe -> input.material().equals(getRecipeInput(recipe))).findFirst();\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CraftingTableBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.inventory.InventoryCloseEvent;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic class CraftingTableBehaviour extends VanillaBlockBehaviour {\n    public CraftingTableBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context);\n    }\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Inventory inventory = new Inventory(InventoryType.CRAFTING, \"Crafting Table\");\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        player.eventNode().addListener(\n                EventListener.builder(InventoryCloseEvent.class)\n                        .filter(event -> inventory == event.getInventory())\n                        .expireCount(1)\n                        .handler(event -> {\n                            // TODO: Drop all items instead of adding them to the player's inventory?\n                            for (ItemStack itemStack : inventory.getItemStacks()) {\n                                player.getInventory().addItemStack(itemStack);\n                            }\n                        })\n                        .build()\n        );\n        return false;\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/FurnaceBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour;\nimport net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory;\nimport net.minestom.vanilla.events.FurnaceTickEvent;\nimport org.jetbrains.annotations.NotNull;\n\npublic class FurnaceBehaviour extends InventoryBlockBehaviour {\n    public FurnaceBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context, InventoryType.FURNACE, Component.text(\"Furnace\"));\n    }\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.FURNACE, Component.text(\"Furnace\"));\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        return false;\n    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return true;\n    }\n\n    @Override\n    public boolean isTickable() {\n        return true;\n    }\n\n    @Override\n    public void tick(@NotNull BlockHandler.Tick tick) {\n        var events = this.context.vri().process().eventHandler();\n        if (!events.hasListener(FurnaceTickEvent.class)) return; // fast exit since this is hot code\n        Instance instance = tick.getInstance();\n        Point pos = tick.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.FURNACE, Component.text(\"Furnace\"));\n        FurnaceTickEvent event = new FurnaceTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory);\n        events.call(event);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmithingTableBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.inventory.InventoryCloseEvent;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic class SmithingTableBehaviour extends VanillaBlockBehaviour {\n    public SmithingTableBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context);\n    }\n\n    // TODO: block placement facing\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Inventory inventory = new Inventory(InventoryType.SMITHING, \"Upgrade Gear\");\n\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        player.eventNode().addListener(\n                EventListener.builder(InventoryCloseEvent.class)\n                        .filter(event -> inventory == event.getInventory())\n                        .expireCount(1)\n                        .handler(event -> {\n                            // TODO: Drop all items instead of adding them to the player's inventory?\n                            for (int i = 0; i < 3; i++) {\n                                player.getInventory().addItemStack(inventory.getItemStack(i));\n                            }\n                        })\n                        .build()\n        );\n        return false;\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmokerBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour;\nimport net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory;\nimport net.minestom.vanilla.events.SmokerTickEvent;\nimport org.jetbrains.annotations.NotNull;\n\npublic class SmokerBehaviour extends InventoryBlockBehaviour {\n    public SmokerBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context, InventoryType.SMOKER, Component.text(\"Smoker\"));\n    }\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Instance instance = interaction.getInstance();\n        Point pos = interaction.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.SMOKER, Component.text(\"Smoker\"));\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        return false;\n    }\n\n    @Override\n    public boolean dropContentsOnDestroy() {\n        return true;\n    }\n\n    @Override\n    public boolean isTickable() {\n        return true;\n    }\n\n    @Override\n    public void tick(@NotNull BlockHandler.Tick tick) {\n        var events = this.context.vri().process().eventHandler();\n        if (!events.hasListener(SmokerTickEvent.class)) return; // fast exit since this is hot code\n        Instance instance = tick.getInstance();\n        Point pos = tick.getBlockPosition();\n        Inventory inventory = BlockInventory.from(instance, pos, InventoryType.SMOKER, Component.text(\"Smoker\"));\n        SmokerTickEvent event = new SmokerTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory);\n        events.call(event);\n    }\n}\n"
  },
  {
    "path": "blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/StonecutterBehaviour.java",
    "content": "package net.minestom.vanilla.blocks.behaviours.recipe;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.inventory.InventoryCloseEvent;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.blocks.VanillaBlockBehaviour;\nimport net.minestom.vanilla.blocks.VanillaBlocks;\nimport org.jetbrains.annotations.NotNull;\n\npublic class StonecutterBehaviour extends VanillaBlockBehaviour {\n    public StonecutterBehaviour(VanillaBlocks.@NotNull BlockContext context) {\n        super(context);\n    }\n\n    // TODO: block placement facing\n\n    @Override\n    public boolean onInteract(@NotNull BlockHandler.Interaction interaction) {\n        Inventory inventory = new Inventory(InventoryType.STONE_CUTTER, \"Stonecutter\");\n\n        Player player = interaction.getPlayer();\n        player.openInventory(inventory);\n        player.eventNode().addListener(\n                EventListener.builder(InventoryCloseEvent.class)\n                        .filter(event -> inventory == event.getInventory())\n                        .expireCount(1)\n                        .handler(event -> {\n                            // TODO: Drop all items instead of adding them to the player's inventory?\n                            player.getInventory().addItemStack(inventory.getItemStack(0));\n                        })\n                        .build()\n        );\n        return false;\n    }\n}\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "plugins {\n    java\n    `java-library`\n    `maven-publish`\n    id(\"com.github.harbby.gradle.serviceloader\") version (\"1.1.8\")\n    id(\"io.github.goooler.shadow\") version (\"8.1.8\")\n}\n\nsubprojects {\n\n    plugins.apply(\"java\")\n    plugins.apply(\"java-library\")\n    plugins.apply(\"maven-publish\")\n    plugins.apply(\"com.github.harbby.gradle.serviceloader\")\n    plugins.apply(\"io.github.goooler.shadow\")\n\n    group = \"net.minestom.vanilla\"\n    version = \"indev\"\n\n    java {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n\n//        withJavadocJar()\n        withSourcesJar()\n\n        sourceSets.main {\n            java.srcDir(\"src/main/java\")\n        }\n    }\n\n    tasks.withType<Jar> {\n        duplicatesStrategy = DuplicatesStrategy.INCLUDE\n    }\n\n    tasks.withType<Wrapper> {\n        gradleVersion = rootProject.gradle.gradleVersion\n    }\n\n    repositories {\n        mavenCentral()\n        maven(url = \"https://jitpack.io\")\n        mavenLocal()\n    }\n\n    dependencies {\n    }\n\n    publishing {\n        publications {\n            register(\"maven\", MavenPublication::class) {\n                from(components[\"java\"])\n            }\n        }\n    }\n\n    serviceLoader.serviceInterfaces.add(\"net.minestom.vanilla.VanillaReimplementation\\$Feature\")\n    serviceLoader.serviceInterfaces.add(\"org.slf4j.spi.SLF4JServiceProvider\")\n\n    tasks.getByName(\"build\").dependsOn(\"shadowJar\")\n\n    tasks.withType<Test> {\n        dependsOn(\"serviceLoaderBuild\")\n        useJUnitPlatform()\n    }\n}\n"
  },
  {
    "path": "commands/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":instance-meta\"))\n}"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/DifficultyCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\nimport net.minestom.server.command.builder.arguments.Argument;\nimport net.minestom.server.command.builder.arguments.ArgumentType;\nimport net.minestom.server.command.builder.exception.ArgumentSyntaxException;\nimport net.minestom.server.world.Difficulty;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Command that make an instance change difficulty\n */\npublic class DifficultyCommand extends Command {\n    public DifficultyCommand() {\n        super(\"difficulty\");\n\n        setCondition(this::isAllowed);\n\n        setDefaultExecutor(this::usage);\n\n        Argument<?> difficulty = ArgumentType.Word(\"difficulty\").from(\"peaceful\", \"easy\", \"normal\", \"hard\");\n\n\n        difficulty.setCallback(this::difficultyCallback);\n\n        addSyntax(this::execute, difficulty);\n    }\n\n    private void usage(CommandSender player, CommandContext arguments) {\n        player.sendMessage(\"Usage: /difficulty (peaceful|easy|normal|hard)\");\n    }\n\n    private void execute(CommandSender player, CommandContext arguments) {\n        String difficultyName = arguments.get(\"difficulty\");\n        Difficulty difficulty = Difficulty.valueOf(difficultyName.toUpperCase());\n        MinecraftServer.setDifficulty(difficulty);\n        player.sendMessage(\"You are now playing in \" + difficultyName);\n    }\n\n    private void difficultyCallback(@NotNull CommandSender sender, @NotNull ArgumentSyntaxException exception) {\n        sender.sendMessage(\"'\" + exception.getInput() + \"' is not a valid difficulty!\");\n    }\n\n    private boolean isAllowed(CommandSender player, String commandName) {\n        return true; // TODO: permissions\n    }\n}\n\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/ForceloadCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\nimport net.minestom.server.command.builder.arguments.ArgumentType;\nimport net.minestom.server.coordinate.CoordConversion;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.utils.location.RelativeVec;\nimport net.minestom.vanilla.instancemeta.tickets.TicketManager;\nimport net.minestom.vanilla.instancemeta.tickets.TicketUtils;\n\nimport java.util.List;\n\n/**\n * \"forceload\":\n * Description: \"Forces chunks to constantly be loaded or not. \"\n * BE: false\n * EE: false\n * JE: true\n * OP_Level: 2\n * BE_EE_OP_Level: 0\n * MP_Only: false\n */\npublic class ForceloadCommand extends Command {\n\n    public ForceloadCommand() {\n        super(\"forceload\");\n\n        // forceload add <from> [<to>]\n        //    Forces the chunk at the <from> position (through to <to> if set) in the dimension of the command's execution to be loaded constantly.\n        this.addSyntax(\n                this::usageAddFrom,\n                ArgumentType.Literal(\"add\"),\n                ArgumentType.RelativeVec2(\"from\")\n        );\n        this.addSyntax(\n                this::usageAddFromTo,\n                ArgumentType.Literal(\"add\"),\n                ArgumentType.RelativeVec2(\"from\"),\n                ArgumentType.RelativeVec2(\"to\")\n        );\n\n        // forceload remove <from> [<to>]\n        //    Unforces the chunk at the <from> position (through to <to> if set) in the dimension of the command's execution to be loaded constantly.\n        this.addSyntax(\n                this::usageRemoveFrom,\n                ArgumentType.Literal(\"remove\"),\n                ArgumentType.RelativeVec2(\"from\")\n        );\n        this.addSyntax(\n                this::usageRemoveFromTo,\n                ArgumentType.Literal(\"remove\"),\n                ArgumentType.RelativeVec2(\"from\"),\n                ArgumentType.RelativeVec2(\"to\")\n        );\n    }\n\n    private void addForceLoad(Instance instance, int chunkX, int chunkZ) {\n        addForceLoad(instance, CoordConversion.chunkIndex(chunkX, chunkZ));\n    }\n\n    private void addForceLoad(Instance instance, long chunkIndex) {\n        TicketManager.Ticket ticketToAdd = TicketManager.Ticket.from(TicketManager.FORCED_TICKET, chunkIndex);\n        TicketUtils.waitingTickets(instance, List.of(ticketToAdd));\n    }\n\n    private void removeForceLoad(Instance instance, int chunkX, int chunkZ) {\n        removeForceLoad(instance, CoordConversion.chunkIndex(chunkX, chunkZ));\n    }\n\n    private void removeForceLoad(Instance instance, long chunkIndex) {\n\n        TicketManager.Ticket ticketToRemove = TicketManager.Ticket.from(TicketManager.FORCED_TICKET, chunkIndex);\n        TicketUtils.removingTickets(instance, List.of(ticketToRemove));\n    }\n\n    private void usageAddFrom(CommandSender sender, CommandContext context) {\n        if (!(sender instanceof Player player)) {\n            sender.sendMessage(\"This command must be executed by a player!\");\n            return;\n        }\n        RelativeVec fromVec = context.get(\"from\");\n        Vec position = fromVec.from(player.getPosition());\n\n        // Get chunk position\n        int chunkX = CoordConversion.globalToChunk(position.x());\n        int chunkZ = CoordConversion.globalToChunk(position.z());\n\n        // Add the force load\n        Instance instance = player.getInstance();\n        addForceLoad(instance, chunkX, chunkZ);\n    }\n\n    private void usageAddFromTo(CommandSender sender, CommandContext context) {\n        if (!(sender instanceof Player player)) {\n            sender.sendMessage(\"This command must be executed by a player!\");\n            return;\n        }\n        RelativeVec fromVec = context.get(\"from\");\n        RelativeVec toVec = context.get(\"to\");\n        Vec from = fromVec.from(player.getPosition());\n        Vec to = toVec.from(player.getPosition());\n\n        int startX = Math.min(from.blockX(), to.blockX());\n        int endX = Math.max(from.blockX(), to.blockX());\n        int startZ = Math.min(from.blockZ(), to.blockZ());\n        int endZ = Math.max(from.blockZ(), to.blockZ());\n\n        Instance instance = player.getInstance();\n\n        for (int offX = startX; offX < endX; offX += 16) {\n            for (int offZ = startZ; offZ < endZ; offZ += 16) {\n                // Get chunk position\n                int chunkX = CoordConversion.globalToChunk(offX);\n                int chunkZ = CoordConversion.globalToChunk(offZ);\n                removeForceLoad(instance, chunkX, chunkZ);\n            }\n        }\n    }\n\n    private void usageRemoveFrom(CommandSender sender, CommandContext context) {\n        if (!(sender instanceof Player player)) {\n            sender.sendMessage(\"This command must be executed by a player!\");\n            return;\n        }\n        RelativeVec fromVec = context.get(\"from\");\n        Vec position = fromVec.from(player.getPosition());\n\n        // Get chunk position\n        int chunkX = CoordConversion.globalToChunk(position.x());\n        int chunkZ = CoordConversion.globalToChunk(position.z());\n\n        // Remove force load\n        Instance instance = player.getInstance();\n\n        removeForceLoad(instance, chunkX, chunkZ);\n    }\n\n    private void usageRemoveFromTo(CommandSender sender, CommandContext context) {\n        if (!(sender instanceof Player player)) {\n            sender.sendMessage(\"This command must be executed by a player!\");\n            return;\n        }\n        RelativeVec fromVec = context.get(\"from\");\n        RelativeVec toVec = context.get(\"to\");\n        Vec from = fromVec.from(player.getPosition());\n        Vec to = toVec.from(player.getPosition());\n\n        int minX = Math.min(from.blockX(), to.blockX());\n        int maxX = Math.max(from.blockX(), to.blockX());\n        int minZ = Math.min(from.blockZ(), to.blockZ());\n        int maxZ = Math.max(from.blockZ(), to.blockZ());\n\n        Instance instance = player.getInstance();\n\n        for (int offX = minX; offX <= maxX; offX += 16) {\n            for (int offZ = minZ; offZ <= maxZ; offZ += 16) {\n                // Get chunk position\n                int chunkX = CoordConversion.globalToChunk(offX);\n                int chunkZ = CoordConversion.globalToChunk(offZ);\n\n                // Remove the force load\n                removeForceLoad(instance, chunkX, chunkZ);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/GamemodeCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.format.NamedTextColor;\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.arguments.ArgumentEnum;\nimport net.minestom.server.command.builder.arguments.ArgumentType;\nimport net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.GameMode;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.utils.entity.EntityFinder;\n\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * Command that make a player change gamemode, made in\n * the style of the vanilla /gamemode command.\n *\n * @see <a href=\"https://minecraft.fandom.com/wiki/Commands/gamemode\">...</a>\n */\npublic class GamemodeCommand extends Command {\n\n    public GamemodeCommand() {\n        super(\"gamemode\", \"gm\");\n\n        //GameMode parameter\n        ArgumentEnum<GameMode> gamemode = ArgumentType.Enum(\"gamemode\", GameMode.class).setFormat(ArgumentEnum.Format.LOWER_CASED);\n        gamemode.setCallback((sender, exception) -> sender.sendMessage(\n                Component.text(\"Invalid gamemode \", NamedTextColor.RED)\n                        .append(Component.text(exception.getInput(), NamedTextColor.WHITE))\n                        .append(Component.text(\"!\"))));\n\n        ArgumentEntity player = ArgumentType.Entity(\"targets\").onlyPlayers(true);\n\n        //Upon invalid usage, print the correct usage of the command to the sender\n        setDefaultExecutor((sender, context) -> {\n            String commandName = context.getCommandName();\n\n            sender.sendMessage(Component.text(\"Usage: /\" + commandName + \" <gamemode> [targets]\", NamedTextColor.RED));\n        });\n\n        //Command Syntax for /gamemode <gamemode>\n        addSyntax((sender, context) -> {\n            //Limit execution to players only\n            if (!(sender instanceof Player playerSender)) {\n                sender.sendMessage(Component.text(\"Please run this command in-game.\", NamedTextColor.RED));\n                return;\n            }\n\n            //Check permission, this could be replaced with hasPermission\n            if (playerSender.getPermissionLevel() < 2) {\n                sender.sendMessage(Component.text(\"You don't have permission to use this command.\", NamedTextColor.RED));\n                return;\n            }\n\n            GameMode mode = context.get(gamemode);\n\n            //Set the gamemode for the sender\n            executeSelf(playerSender, mode);\n        }, gamemode);\n\n        //Command Syntax for /gamemode <gamemode> [targets]\n        addSyntax((sender, context) -> {\n            //Check permission for players only\n            //This allows the console to use this syntax too\n            if ((sender instanceof Player playerSender) && playerSender.getPermissionLevel() < 2) {\n                sender.sendMessage(Component.text(\"You don't have permission to use this command.\", NamedTextColor.RED));\n                return;\n            }\n\n            EntityFinder finder = context.get(player);\n            GameMode mode = context.get(gamemode);\n\n            //Set the gamemode for the targets\n            executeOthers(sender, mode, finder.find(sender));\n        }, gamemode, player);\n    }\n\n    /**\n     * Sets the gamemode for the specified entities, and\n     * notifies them (and the sender) in the chat.\n     */\n    private void executeOthers(CommandSender sender, GameMode mode, List<Entity> entities) {\n        if (entities.isEmpty()) {\n            //If there are no players that could be modified, display an error message\n            if (sender instanceof Player playerSender)\n                sender.sendMessage(Component.translatable(\"argument.entity.notfound.player\", NamedTextColor.RED));\n            else sender.sendMessage(Component.text(\"No player was found\", NamedTextColor.RED));\n        } else for (Entity entity : entities) {\n            if (entity instanceof Player p) {\n                if (p == sender) {\n                    //If the player is the same as the sender, call\n                    //executeSelf to display one message instead of two\n                    executeSelf(p, mode);\n                } else {\n                    p.setGameMode(mode);\n\n                    String gamemodeString = \"gameMode.\" + mode.name().toLowerCase(Locale.ROOT);\n                    Component gamemodeComponent = Component.translatable(gamemodeString);\n                    Component playerName = p.getDisplayName() == null ? p.getName() : p.getDisplayName();\n\n                    //Send a message to the changed player and the sender\n                    p.sendMessage(Component.translatable(\"gameMode.changed\", gamemodeComponent));\n                    sender.sendMessage(Component.translatable(\"commands.gamemode.success.other\", playerName, gamemodeComponent));\n                }\n            }\n        }\n    }\n\n    /**\n     * Sets the gamemode for the executing Player, and\n     * notifies them in the chat.\n     */\n    private void executeSelf(Player sender, GameMode mode) {\n        sender.setGameMode(mode);\n\n        //The translation keys 'gameMode.survival', 'gameMode.creative', etc.\n        //correspond to the translated game mode names.\n        String gamemodeString = \"gameMode.\" + mode.name().toLowerCase(Locale.ROOT);\n        Component gamemodeComponent = Component.translatable(gamemodeString);\n\n        //Send the translated message to the player.\n        sender.sendMessage(Component.translatable(\"commands.gamemode.success.self\", gamemodeComponent));\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/HelpCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Returns the list of all available commands\n */\npublic class HelpCommand extends Command {\n    public HelpCommand() {\n        super(\"help\");\n\n        setDefaultExecutor(this::execute);\n    }\n\n    private void execute(CommandSender sender, CommandContext context) {\n        sender.sendMessage(\"=== Help ===\");\n\n        List<VanillaCommands> commands = new ArrayList<>();\n\n        Collections.addAll(commands, VanillaCommands.values());\n\n        commands.sort(this::compareCommands);\n\n        commands.forEach(command -> sender.sendMessage(\"/\" + command.name().toLowerCase()));\n\n        sender.sendMessage(\"============\");\n    }\n\n    private int compareCommands(VanillaCommands a, VanillaCommands b) {\n        return a.name().compareTo(b.name());\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/MeCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.TextComponent;\nimport net.minestom.server.adventure.audience.Audiences;\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\nimport net.minestom.server.command.builder.arguments.ArgumentStringArray;\nimport net.minestom.server.command.builder.arguments.ArgumentType;\nimport net.minestom.server.entity.Player;\n\n/**\n * Command that displays a player action\n */\npublic class MeCommand extends Command {\n    public MeCommand() {\n        super(\"me\");\n\n        setDefaultExecutor(this::usage);\n\n        ArgumentStringArray message = ArgumentType.StringArray(\"message\");\n\n        addSyntax(this::execute, message);\n    }\n\n    private void usage(CommandSender player, CommandContext arguments) {\n        player.sendMessage(\"Usage: /me <message>\");\n    }\n\n    private void execute(CommandSender sender, CommandContext arguments) {\n        if (!(sender instanceof Player player)) {\n            sender.sendMessage(\"This command must be executed by a player!\");\n            return;\n        }\n        String[] messageParts = arguments.get(\"message\");\n\n        TextComponent.Builder builder = Component.text();\n\n        builder.append(Component.text(\" * \" + player.getUsername()));\n\n        builder.append(Component.text(\" \"));\n        builder.append(Component.text(messageParts[0]));\n\n        for (int i = 1; i < messageParts.length; i++) {\n            builder.append(Component.text(messageParts[i]));\n        }\n\n        Component message = builder.build();\n        Audiences.all().sendMessage(message);\n    }\n\n    @SuppressWarnings(\"unused\")\n    private boolean isAllowed(CommandSender player) {\n        return player instanceof Player; // TODO: permissions\n    }\n}\n\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/SaveAllCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\nimport net.minestom.vanilla.logging.Logger;\n\n/**\n * Save the server\n */\npublic class SaveAllCommand extends Command {\n    public SaveAllCommand() {\n        super(\"save-all\");\n        setCondition(this::condition);\n        setDefaultExecutor(this::execute);\n    }\n\n    private boolean condition(CommandSender player, String commandName) {\n        return true; // TODO: permissions\n    }\n\n    private void execute(CommandSender player, CommandContext arguments) {\n        MinecraftServer.getInstanceManager().getInstances().forEach(i -> {\n            i.saveChunksToStorage();\n            Logger.info(\"Saved dimension \" + i.getDimensionType().name());\n        });\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/StopCommand.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.command.CommandSender;\nimport net.minestom.server.command.builder.Command;\nimport net.minestom.server.command.builder.CommandContext;\n\n/**\n * Stops the server\n */\npublic class StopCommand extends Command {\n    public StopCommand() {\n        super(\"stop\");\n        setCondition(this::condition);\n        setDefaultExecutor(this::execute);\n    }\n\n    private boolean condition(CommandSender player, String commandName) {\n        return true; // TODO: permissions\n    }\n\n    private void execute(CommandSender player, CommandContext arguments) {\n        MinecraftServer.stopCleanly();\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/VanillaCommands.java",
    "content": "package net.minestom.vanilla.commands;\n\nimport net.minestom.server.command.CommandManager;\nimport net.minestom.server.command.builder.Command;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * All commands available in the vanilla reimplementation\n */\npublic enum VanillaCommands {\n\n    FORCELOAD(ForceloadCommand::new),\n    GAMEMODE(GamemodeCommand::new),\n    DIFFICULTY(DifficultyCommand::new),\n    ME(MeCommand::new),\n    STOP(StopCommand::new),\n    HELP(HelpCommand::new),\n    SAVE_ALL(SaveAllCommand::new),\n    ;\n\n    private final Supplier<Command> commandCreator;\n\n    VanillaCommands(Supplier<Command> commandCreator) {\n        this.commandCreator = commandCreator;\n    }\n\n    /**\n     * Register all vanilla commands into the given manager\n     *\n     * @param manager the command manager to register commands on\n     */\n    public static void registerAll(@NotNull CommandManager manager) {\n        for (VanillaCommands vanillaCommand : values()) {\n            Command command = vanillaCommand.commandCreator.get();\n            manager.register(command);\n        }\n    }\n}\n"
  },
  {
    "path": "commands/src/main/java/net/minestom/vanilla/commands/VanillaCommandsFeature.java",
    "content": "package net.minestom.vanilla.commands;\n\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.instancemeta.InstanceMetaFeature;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\n\npublic class VanillaCommandsFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        new Logic().hook(context.vri());\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:commands\");\n    }\n\n    private static class Logic {\n        private Logic() {\n        }\n\n        private void hook(@NotNull VanillaReimplementation vri) {\n            VanillaCommands.registerAll(vri.process().command());\n        }\n    }\n\n    @Override\n    public @NotNull Set<Class<? extends VanillaReimplementation.Feature>> dependencies() {\n        return Set.of(InstanceMetaFeature.class);\n    }\n}\n"
  },
  {
    "path": "core/build.gradle.kts",
    "content": "dependencies {\n    // Minestom\n    api(\"net.minestom:minestom:${project.property(\"minestom_version\")}\")\n\n    // Raycasting\n    api(\"com.github.EmortalMC:Rayfast:${project.property(\"rayfast_version\")}\")\n\n    // Noise\n    api(\"com.github.Articdive:JNoise:${project.property(\"jnoise_version\")}\")\n\n    // Annotations\n    api(\"org.jetbrains:annotations:${project.property(\"annotations_version\")}\")\n\n    // SLF4j\n    api(\"org.slf4j:slf4j-api:${project.property(\"slf4j_version\")}\")\n\n    // Json\n    api(\"com.squareup.moshi:moshi:1.14.0\")\n    api(\"com.squareup.moshi:moshi-adapters:1.14.0\")\n\n    // Tests\n    testImplementation(platform(\"org.junit:junit-bom:5.9.1\"))\n    testImplementation(\"org.junit.jupiter:junit-jupiter\")\n}\n\ntasks.test {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/VanillaRegistry.java",
    "content": "package net.minestom.vanilla;\n\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.tag.TagReadable;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * This registry object is used to register data and logic.\n */\npublic sealed interface VanillaRegistry permits VanillaReimplementationImpl.VanillaRegistryImpl {\n\n    /**\n     * Registers an entity type to its entity spawner.\n     *\n     * @param type     the type of the entity\n     * @param supplier the entity spawner of the entity\n     */\n    void register(@NotNull EntityType type, @NotNull EntitySpawner supplier);\n\n    interface EntitySpawner {\n        @NotNull Entity spawn(@NotNull VanillaRegistry.EntityContext context);\n    }\n\n    interface EntityContext extends TagReadable {\n        @NotNull EntityType type();\n\n        @NotNull Pos position();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/VanillaReimplementation.java",
    "content": "package net.minestom.vanilla;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.tag.TagWritable;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.logging.StatusUpdater;\nimport net.minestom.vanilla.utils.DependencySorting;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\npublic interface VanillaReimplementation {\n\n    /**\n     * Creates a new instance of {@link VanillaReimplementation} and hooks into the server process.\n     *\n     * @param process the server process\n     * @return the new instance\n     */\n    static @NotNull VanillaReimplementation hook(@NotNull ServerProcess process) {\n        return VanillaReimplementation.hook(process, feature -> true);\n    }\n\n    /**\n     * Creates a new instance of {@link VanillaReimplementation} and hooks all features into the server process.\n     * <p>\n     * This method only hooks the features that pass the given predicate.\n     * </p>\n     *\n     * @param process   the server process\n     * @param predicate the predicate to test the features\n     * @return the new instance\n     */\n    static @NotNull VanillaReimplementation hook(@NotNull ServerProcess process, Predicate<Feature> predicate) {\n        return VanillaReimplementationImpl.hook(process, predicate);\n    }\n\n    // Vri Methods\n\n    /**\n     * @return the server process\n     */\n    @NotNull ServerProcess process();\n\n    /**\n     * Acquires the given {@link Feature} from this reimplementation.\n     * @param clazz the feature's class\n     * @param <T> the feature's type\n     * @return the feature\n     */\n    <T extends Feature> @NotNull T feature(Class<T> clazz);\n\n    /**\n     * Creates an {@link net.minestom.vanilla.VanillaRegistry.EntityContext} for the given type and position\n     *\n     * @param type     the type of the entity\n     * @param position the position of the entity at spawn\n     * @return the context\n     */\n    default @NotNull VanillaRegistry.EntityContext entityContext(EntityType type, Point position) {\n        return entityContext(type, position, writer -> {\n        });\n    }\n\n    /**\n     * Creates an {@link net.minestom.vanilla.VanillaRegistry.EntityContext} for the given type and position, with the\n     * given tag values.\n     *\n     * @param type     the type of the entity\n     * @param position the position of the entity at spawn\n     * @return the context\n     */\n    @NotNull VanillaRegistry.EntityContext entityContext(EntityType type, Point position,\n                                                         @NotNull Consumer<TagWritable> tagWriter);\n\n    /**\n     * Creates a new vanilla entity, using the specified context, returning null if the entity type is not implemented.\n     *\n     * @param context the context\n     * @return the new entity\n     */\n    @Nullable Entity createEntity(@NotNull VanillaRegistry.EntityContext context);\n\n    /**\n     * Creates a new vanilla entity, using the specified context, returning a dummy entity if the entity type is not\n     * implemented.\n     *\n     * @param context the context\n     * @return the new entity\n     */\n    @NotNull Entity createEntityOrDummy(@NotNull VanillaRegistry.EntityContext context);\n\n    /**\n     * Creates and registers a vanilla instance.\n     */\n    @NotNull Instance createInstance(@NotNull Key namespace, @NotNull DimensionType dimension);\n\n    /**\n     * Gets a registered vanilla instance.\n     *\n     * @param namespace the namespace of the instance\n     * @return the instance, or null if not found\n     */\n    @Nullable Instance getInstance(Key namespace);\n\n    /**\n     * Retrieves or generates a random object unique to the given object.\n     * <br>\n     * Note that this method does not keep the given key in memory, however it does always return the same random for\n     * any given (equal) key object.\n     *\n     * @param key the key\n     * @return the random\n     */\n    @NotNull Random random(@NotNull Object key);\n\n    /**\n     * A feature is a collection of logic that can be hooked into a server process.\n     */\n    interface Feature extends DependencySorting.NamespaceDependent<Class<? extends Feature>> {\n\n        /**\n         * Hooks into this server process.\n         * <p>\n         * DO NOT manually call this method, use {@link VanillaReimplementation#hook} instead.\n         * </p>\n         *\n         * @param vri      the vanilla reimplementation object\n         * @param registry the registry object\n         */\n        @Deprecated\n        default void hook(@NotNull VanillaReimplementation vri, @NotNull VanillaRegistry registry) {\n        }\n\n        /**\n         * Hooks into this server process.\n         * <p>\n         * DO NOT manually call this method, use {@link VanillaReimplementation#hook} instead.\n         * </p>\n         *\n         * @param context the context containing all related objects\n         */\n        void hook(@NotNull HookContext context);\n\n        interface HookContext {\n            @NotNull VanillaReimplementation vri();\n            @NotNull VanillaRegistry registry();\n            @NotNull StatusUpdater status();\n        }\n\n        /**\n         * Obtains the dependencies of this feature.\n         * These dependencies will be loaded before this feature.\n         * @return the dependencies\n         */\n        default @NotNull Set<Class<? extends Feature>> dependencies() {\n            return Set.of();\n        }\n\n        /**\n         * @return a unique {@link Key} for this feature\n         */\n        @NotNull Key key();\n\n        /**\n         * @return a unique {@link Class} for this feature\n         */\n        default @NotNull Class<? extends Feature> identity() {\n            return getClass();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/VanillaReimplementationImpl.java",
    "content": "package net.minestom.vanilla;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.InstanceContainer;\nimport net.minestom.server.instance.LightingChunk;\nimport net.minestom.server.instance.anvil.AnvilLoader;\nimport net.minestom.server.registry.RegistryKey;\nimport net.minestom.server.tag.TagHandler;\nimport net.minestom.server.tag.TagWritable;\nimport net.minestom.server.tag.Taggable;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.dimensions.VanillaDimensionTypes;\nimport net.minestom.vanilla.instance.SetupVanillaInstanceEvent;\nimport net.minestom.vanilla.logging.Loading;\nimport net.minestom.vanilla.logging.Logger;\nimport net.minestom.vanilla.logging.StatusUpdater;\nimport net.minestom.vanilla.utils.DependencySorting;\nimport net.minestom.vanilla.utils.MinestomUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\nclass VanillaReimplementationImpl implements VanillaReimplementation {\n\n    private final ServerProcess process;\n    private final Map<Key, Instance> worlds = new ConcurrentHashMap<>();\n    private final Map<EntityType, VanillaRegistry.EntitySpawner> entity2Spawner = new ConcurrentHashMap<>();\n    private final Map<Class<Feature>, Feature> class2Feature = new ConcurrentHashMap<>();\n    private final Map<Object, Random> randoms = Collections.synchronizedMap(new WeakHashMap<>());\n\n    private VanillaReimplementationImpl(@NotNull ServerProcess process) {\n        this.process = process;\n    }\n\n    /**\n     * Creates a new instance of {@link VanillaReimplementationImpl} and hooks into the server process.\n     *\n     * @param process   the server process\n     * @param predicate a predicate to determine which features to enable\n     * @return the new instance\n     */\n    public static @NotNull VanillaReimplementationImpl hook(@NotNull ServerProcess process, Predicate<Feature> predicate) {\n        Loading.start(\"Initialising\");\n\n        Loading.start(\"Initialising Minestom Resources...\");\n        MinestomUtils.initialize();\n        Loading.finish();\n\n        Loading.updater().progress(0.33);\n\n        Loading.start(\"Instantiating vri\");\n        VanillaReimplementationImpl vri = new VanillaReimplementationImpl(process);\n        Loading.finish();\n\n        Loading.updater().progress(0.66);\n        vri.INTERNAL_HOOK(predicate);\n        Loading.updater().progress(1);\n\n        Loading.finish();\n\n        return vri;\n    }\n\n    // Vri Methods\n\n    /**\n     * @return the server process\n     */\n    public @NotNull ServerProcess process() {\n        return process;\n    }\n\n    @Override\n    public <T extends Feature> @NotNull T feature(Class<T> clazz) {\n        //noinspection unchecked\n        return (T) Objects.requireNonNull(class2Feature.get(clazz), () -> \"Feature \" + clazz + \" has not loaded yet.\");\n    }\n\n\n    /**\n     * Creates an {@link VanillaRegistry.EntityContext} for the given type and position\n     *\n     * @param type     the type of the entity\n     * @param position the position of the entity at spawn\n     * @return the context\n     */\n    public @NotNull VanillaRegistry.EntityContext entityContext(EntityType type, Point position) {\n        return new EntityContextImpl(type, Pos.fromPoint(position));\n    }\n\n    /**\n     * Creates an {@link VanillaRegistry.EntityContext} for the given type and position, with the\n     * given tag values.\n     *\n     * @param type     the type of the entity\n     * @param position the position of the entity at spawn\n     * @return the context\n     */\n    public @NotNull VanillaRegistry.EntityContext entityContext(EntityType type, Point position,\n                                                                @NotNull Consumer<TagWritable> tagWriter) {\n        EntityContextImpl impl = new EntityContextImpl(type, Pos.fromPoint(position));\n        tagWriter.accept(impl);\n        return impl;\n    }\n\n    private record EntityContextImpl(@NotNull EntityType type, @NotNull Pos position,\n                                     @NotNull TagHandler tagHandler) implements VanillaRegistry.EntityContext, Taggable {\n        public EntityContextImpl(@NotNull EntityType type, @NotNull Pos position) {\n            this(type, position, TagHandler.newHandler());\n        }\n    }\n\n    /**\n     * Creates a new vanilla entity, using the specified context, returning null if the entity type is not implemented.\n     *\n     * @param context the context\n     * @return the new entity\n     */\n    public @Nullable Entity createEntity(@NotNull VanillaRegistry.EntityContext context) {\n        // Get the spawner\n        VanillaRegistry.EntitySpawner spawner = entity2Spawner.get(context.type());\n\n        // Create the entity\n        return spawner != null ? spawner.spawn(context) : null;\n    }\n\n    /**\n     * Creates a new vanilla entity, using the specified context, returning a dummy entity if the entity type is not\n     * implemented.\n     *\n     * @param context the context\n     * @return the new entity\n     */\n    public @NotNull Entity createEntityOrDummy(@NotNull VanillaRegistry.EntityContext context) {\n        Entity entity = createEntity(context);\n        return entity != null ? entity : new DummyEntity(context.type());\n    }\n\n    private static class DummyEntity extends Entity {\n        public DummyEntity(@NotNull EntityType type) {\n            super(type);\n        }\n    }\n\n    /**\n     * Creates a vanilla instance.\n     */\n    public @NotNull Instance createInstance(@NotNull Key name, @NotNull DimensionType dimension) {\n        RegistryKey<DimensionType> key = process().dimensionType().getKey(dimension);\n        Objects.requireNonNull(key, \"Dimension type \" + dimension + \" is not registered!\");\n        InstanceContainer instance = process().instance().createInstanceContainer(key);\n        worlds.put(name, instance);\n\n        // Anvil directory\n        AnvilLoader loader = new AnvilLoader(name.value());\n        instance.setChunkLoader(loader);\n        instance.setChunkSupplier(LightingChunk::new);\n\n        // Setup event\n        SetupVanillaInstanceEvent event = new SetupVanillaInstanceEvent(instance);\n        process().eventHandler().call(event);\n\n        return instance;\n    }\n\n    @Override\n    public @NotNull Instance getInstance(Key dimensionId) {\n        return worlds.get(dimensionId);\n    }\n\n    @Override\n    public @NotNull Random random(@NotNull Object key) {\n        return randoms.computeIfAbsent(key, k -> new Random(key.hashCode()));\n    }\n\n    final class VanillaRegistryImpl implements VanillaRegistry {\n        @Override\n        public void register(@NotNull EntityType type, @NotNull EntitySpawner supplier) {\n            entity2Spawner.put(type, supplier);\n        }\n    }\n\n    private void INTERNAL_HOOK(Predicate<Feature> predicate) {\n        // Create the registry\n        VanillaRegistry registry = new VanillaRegistryImpl();\n\n        // Hook this core library\n        Loading.start(\"Hooking Core Library\");\n        hookCoreLibrary();\n        Loading.finish();\n\n        // Load all the features and hook them\n        Loading.start(\"Loading features from classpath\");\n        Set<Feature> features = ServiceLoader.load(Feature.class)\n                .stream()\n                .map(ServiceLoader.Provider::get)\n                .collect(Collectors.toUnmodifiableSet());\n        Loading.finish();\n\n        Loading.start(\"Validating dependencies\");\n        for (Feature feature : features) {\n            try {\n                for (Class<? extends Feature> dependency : feature.dependencies()) {\n                    Objects.requireNonNull(dependency, \"Dependency cannot be null!\");\n                }\n            } catch (Exception e) {\n                Logger.error(\"Failed to load features! Does one of your features have a missing dependency feature?\", e);\n                throw new RuntimeException(e);\n            }\n        }\n        Loading.finish();\n\n        Loading.start(\"Sorting features by dependencies\");\n        List<Feature> sortedByDependencies = DependencySorting.sort(features);\n        Loading.finish();\n\n        for (Feature feature : sortedByDependencies) {\n            if (!predicate.test(feature)) {\n                Logger.info(\"Skipping feature %s...\", feature.key());\n                continue;\n            }\n\n            try {\n                instructHook(feature, registry);\n                //noinspection unchecked\n                class2Feature.put((Class<Feature>) feature.getClass(), feature);\n            } catch (Exception e) {\n                Logger.error(\"Failed to load feature: \" + feature.key(), e);\n                throw new RuntimeException(e);\n            }\n        }\n    }\n\n    private void instructHook(Feature feature, VanillaRegistry registry) {\n        try {\n            Loading.start(\"\" + feature.key());\n            Feature.HookContext context = new HookContextImpl(this, registry, Loading.updater());\n            feature.hook(context);\n        } catch (Exception e) {\n            Logger.error(e, \"Failed to load feature: %s%n\", feature.key());\n            throw new RuntimeException(e);\n        } finally {\n            Loading.finish();\n        }\n    }\n\n    private record HookContextImpl(VanillaReimplementation vri,\n                                       VanillaRegistry registry,\n                                       StatusUpdater status) implements Feature.HookContext {\n    }\n\n    private void hookCoreLibrary() {\n        VanillaDimensionTypes.registerAll(process().dimensionType());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/dimensions/VanillaDimensionTypes.java",
    "content": "package net.minestom.vanilla.dimensions;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.world.DimensionType;\n\nimport java.util.Map;\n\npublic class VanillaDimensionTypes {\n\n    public static final DimensionType OVERWORLD = DimensionType.builder()\n            .build();\n\n    public static Map<DimensionType, Key> values() {\n        return Map.of(\n                OVERWORLD, Key.key(\"vri:overworld\")\n        );\n    }\n\n    public static void registerAll(DynamicRegistry<DimensionType> registry) {\n        values().forEach((dimensionType, namespaceID) -> {\n            registry.register(namespaceID, dimensionType);\n        });\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/events/BlastingFurnaceTickEvent.java",
    "content": "package net.minestom.vanilla.events;\n\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.BlockEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.event.trait.InventoryEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.inventory.Inventory;\n\npublic record BlastingFurnaceTickEvent(Block getBlock, Instance getInstance, BlockVec getBlockPosition,\n                                       Inventory getInventory) implements Event, InstanceEvent, BlockEvent, InventoryEvent {\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/events/FurnaceTickEvent.java",
    "content": "package net.minestom.vanilla.events;\n\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.BlockEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.event.trait.InventoryEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.inventory.Inventory;\n\npublic record FurnaceTickEvent(Block getBlock, Instance getInstance, BlockVec getBlockPosition,\n                               Inventory getInventory) implements Event, InstanceEvent, BlockEvent, InventoryEvent {\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/events/SmokerTickEvent.java",
    "content": "package net.minestom.vanilla.events;\n\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.BlockEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.event.trait.InventoryEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.inventory.Inventory;\n\npublic record SmokerTickEvent(Block getBlock, Instance getInstance, BlockVec getBlockPosition,\n                              Inventory getInventory) implements Event, InstanceEvent, BlockEvent, InventoryEvent {\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/ByteArray.java",
    "content": "package net.minestom.vanilla.files;\n\nimport net.minestom.server.utils.MathUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\n\npublic class ByteArray {\n\n    private final byte[] bytes;\n\n    private ByteArray(byte[] b, boolean copy) {\n        this.bytes = copy ? deepCopy(b) : b;\n    }\n\n    public static ByteArray wrap(byte[] bytes) {\n        return new ByteArray(bytes, false);\n    }\n\n    public static ByteArray copyOf(byte[] bytes) {\n        return new ByteArray(bytes, true);\n    }\n\n    public byte[] array() {\n        return deepCopy(bytes);\n    }\n\n    public int size() {\n        return bytes.length;\n    }\n\n    public byte index(int i) {\n        if (!MathUtils.isBetween(i, 0, size()))\n            throw new ArrayIndexOutOfBoundsException();\n        return bytes[i];\n    }\n\n    public InputStream toStream() {\n        return new ByteArrayInputStream(bytes);\n    }\n\n    public String toCharacterString() {\n        return toCharacterString(StandardCharsets.UTF_8);\n    }\n\n    public String toCharacterString(Charset charset) {\n        return new String(bytes, charset);\n    }\n\n    private byte[] deepCopy(byte[] source) {\n        byte[] copy = new byte[source.length];\n        System.arraycopy(source, 0, copy, 0, source.length);\n        return copy;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        ByteArray byteArray = (ByteArray) o;\n        return Arrays.equals(bytes, byteArray.bytes);\n    }\n\n    @Override\n    public int hashCode() {\n        return Arrays.hashCode(bytes);\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/CacheFileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nclass CacheFileSystem<F> implements FileSystemImpl<F> {\n\n    static final CacheFileSystem<?> EMPTY = new CacheFileSystem<>(new DynamicFileSystem<>());\n\n    private final Map<String, F> files;\n    private final Map<String, FileSystem<F>> folders;\n\n    protected CacheFileSystem(FileSystem<F> original) {\n        this.files = Map.copyOf(original.files().stream()\n                .collect(Collectors.toUnmodifiableMap(Function.identity(), original::file)));\n        this.folders = original.folders().stream()\n                .collect(Collectors.toUnmodifiableMap(Function.identity(),\n                        name -> original.folder(name).cache()));\n    }\n\n    @Override\n    public Set<String> folders() {\n        return folders.keySet();\n    }\n\n    @Override\n    public Set<String> files() {\n        return files.keySet();\n    }\n\n    @Override\n    public FileSystem<F> folder(String path) {\n        return folders.getOrDefault(path, FileSystem.empty());\n    }\n\n    @Override\n    public F file(String path) {\n        return files.get(path);\n    }\n\n    @Override\n    public String toString() {\n        return FileSystemImpl.toString(this);\n    }\n\n    @Override\n    public FileSystem<F> cache() {\n        return this;\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/DynamicFileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\npublic class DynamicFileSystem<F> implements FileSystemImpl<F> {\n\n    protected final Map<String, F> files = new ConcurrentHashMap<>();\n    protected final Map<String, DynamicFileSystem<F>> folders = new ConcurrentHashMap<>();\n\n    protected DynamicFileSystem() {}\n\n    static <F> FileSystem<F> from(FileSystemImpl<F> fileSystem) {\n        DynamicFileSystem<F> dynamicFileSystem = new DynamicFileSystem<>();\n        for (String folder : fileSystem.folders()) {\n            FileSystemImpl<F> subFileSystem = (FileSystemImpl<F>) fileSystem.folder(folder);\n            dynamicFileSystem.folders.put(folder, (DynamicFileSystem<F>) from(subFileSystem));\n        }\n        for (Map.Entry<String, F> entry : fileSystem.files().stream()\n                .collect(Collectors.toUnmodifiableMap(Function.identity(), fileSystem::file)).entrySet()) {\n            dynamicFileSystem.addFile(entry.getKey(), entry.getValue());\n        }\n        return dynamicFileSystem;\n    }\n\n    public DynamicFileSystem<F> addFolder(String directoryName) {\n        int split = directoryName.contains(\"/\") ? directoryName.indexOf('/') : directoryName.length();\n        String folderName = directoryName.substring(0, split);\n        DynamicFileSystem<F> fileSource = folders.computeIfAbsent(folderName, s -> new DynamicFileSystem<>());\n        String remaining = directoryName.substring(directoryName.indexOf(\"/\") + 1);\n        if (remaining.contains(\"/\")) {\n            fileSource.addFolder(remaining);\n        }\n        return fileSource;\n    }\n\n    public void addFile(String name, F contents) {\n        if (!name.contains(\"/\")) {\n            files.put(name, contents);\n            return;\n        }\n\n        String folderName = name.substring(0, name.indexOf(\"/\"));\n        DynamicFileSystem<F> newFileSource = addFolder(folderName);\n        String remaining = name.substring(name.indexOf(\"/\") + 1);\n        newFileSource.addFile(remaining, contents);\n    }\n\n    @Override\n    public Set<String> folders() {\n        return folders.keySet();\n    }\n\n    @Override\n    public Set<String> files() {\n        return files.keySet();\n    }\n\n    @Override\n    public FileSystem<F> folder(@NotNull String path) {\n        var fs = folders.get(path);\n        return fs == null ? FileSystem.empty() : fs;\n    }\n\n    @Override\n    public F file(String path) {\n        return files.get(path);\n    }\n\n    @Override\n    public String toString() {\n        return FileSystemImpl.toString(this);\n    }\n\n    @Override\n    public FileSystem<F> inMemory() {\n        return this;\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/FileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.util.Set;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\npublic interface FileSystem<F> extends FileSystemMappers {\n\n    /**\n     * Queries all the folders in the given directory and returns a set of folder names.\n     *\n     * @return a set of folder names (no directory prefix)\n     */\n    Set<String> folders();\n\n    /**\n     * Queries all the files in the given directory and returns a set of file names.\n     *\n     * @return a set of file names (no directory prefix)\n     */\n    Set<String> files();\n\n    /**\n     * Navigates to the given subdirectory and returns a new FileSource for that directory.\n     *\n     * @param path the path to the subdirectory\n     * @return a new FileSource for the subdirectory\n     */\n    FileSystem<F> folder(String path);\n\n    /**\n     * Reads the file at the given path and returns its contents.\n     * @param path the path to the file\n     * @return the contents of the file\n     */\n    F file(String path);\n\n    FileSystem<F> folder(@NotNull String... paths);\n\n    <T> FileSystem<T> map(Function<F, T> mapper);\n\n    <T> FileSystem<T> map(BiFunction<String, F, T> mapper);\n\n    FileSystem<F> cache();\n\n    FileSystem<F> lazy();\n\n    FileSystem<F> inMemory();\n\n    static <T> FileSystem<T> empty() {\n        //noinspection unchecked\n        return (FileSystem<T>) CacheFileSystem.EMPTY;\n    }\n\n    static FileSystem<ByteArray> fromZipFile(File file, Predicate<String> pathFilter) {\n        return FileSystemUtil.unzipIntoFileSystem(file, pathFilter);\n    }\n\n    default boolean hasFile(String file) {\n        return files().contains(file);\n    }\n\n    default boolean hasFolder(String path) {\n        return folders().contains(path);\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/FileSystemImpl.java",
    "content": "package net.minestom.vanilla.files;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\ninterface FileSystemImpl<F> extends FileSystem<F> {\n\n    default FileSystem<F> folder(@NotNull String... paths) {\n        if (paths.length == 0)\n            return this;\n\n        Iterator<String> iter = Arrays.stream(paths).iterator();\n        FileSystem<F> fs = folder(iter.next());\n\n        while (iter.hasNext()) {\n            fs = fs.folder(iter.next());\n            if (fs.folders().isEmpty())\n                return fs;\n        }\n        return fs;\n    }\n\n    default <T> FileSystem<T> map(Function<F, T> mapper) {\n        return map((str, file) -> mapper.apply(file));\n    }\n\n    default <T> FileSystem<T> map(BiFunction<String, F, T> mapper) {\n        return new MappedFileSystem<>(this, mapper);\n    }\n\n    default FileSystem<F> cache() {\n        return new CacheFileSystem<>(this);\n    }\n\n    default FileSystem<F> lazy() {\n        return new LazyFileSystem<>(this);\n    }\n\n    default FileSystem<F> inMemory() {\n        return DynamicFileSystem.from(this);\n    }\n\n    static String toString(FileSystem<?> fs) {\n        Stream.Builder<String> builder = Stream.builder();\n        toString(fs, builder, 0);\n        return builder.build().collect(Collectors.joining());\n    }\n\n    static void toString(FileSystem<?> fs, Stream.Builder<String> builder, int depth) {\n        String prefix = \"  \".repeat(depth);\n\n        String folderChar = \"\\uD83D\\uDDC0\";\n        String fileChar = \"\\uD83D\\uDDCE\";\n\n        for (String folder : fs.folders()) {\n            builder.add(prefix);\n            builder.add(folderChar);\n            builder.add(\" \");\n            builder.add(folder);\n            builder.add(\"\\n\");\n            toString(fs.folder(folder), builder, depth + 1);\n        }\n\n        for (String file : fs.files()) {\n            builder.add(prefix);\n            builder.add(fileChar);\n            builder.add(\" \");\n            builder.add(file);\n            builder.add(\"\\n\");\n        }\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/FileSystemMappers.java",
    "content": "package net.minestom.vanilla.files;\n\nimport com.google.gson.JsonElement;\n\nimport java.io.InputStream;\nimport java.util.function.Function;\n\ninterface FileSystemMappers {\n    Function<InputStream, ByteArray> INPUT_STREAM_TO_BYTES = inputStream -> {\n        try {\n            return ByteArray.wrap(inputStream.readAllBytes());\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    };\n    Function<ByteArray, String> BYTES_TO_STRING = ByteArray::toCharacterString;\n    Function<String, JsonElement> STRING_TO_JSON = string -> FileSystemUtil.gson.fromJson(string, JsonElement.class);\n\n\n    Function<InputStream, String> INPUT_STREAM_TO_STRING = INPUT_STREAM_TO_BYTES.andThen(BYTES_TO_STRING);\n    Function<InputStream, JsonElement> INPUT_STREAM_TO_JSON = INPUT_STREAM_TO_STRING.andThen(STRING_TO_JSON);\n\n    Function<ByteArray, JsonElement> BYTES_TO_JSON = BYTES_TO_STRING.andThen(STRING_TO_JSON);\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/FileSystemUtil.java",
    "content": "package net.minestom.vanilla.files;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.function.Predicate;\nimport java.util.regex.PatternSyntaxException;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\n\nclass FileSystemUtil {\n\n    static <I extends InputStream> FileSystem<ByteArray> toBytes(FileSystem<I> source) {\n        return source.map(inputStream -> {\n            try {\n                return ByteArray.copyOf(inputStream.readAllBytes());\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        });\n    }\n\n    static <I extends InputStream> FileSystem<String> toString(FileSystem<I> source) {\n        return toBytes(source).map(ByteArray::toString);\n    }\n\n    static <T extends InputStream> FileSystem<JsonElement> toJson(FileSystem<T> source) {\n        Gson gson = new Gson();\n        return toString(source).map(str -> gson.fromJson(str, JsonElement.class));\n    }\n\n    static DynamicFileSystem<ByteArray> unzipIntoFileSystem(@NotNull File file, Predicate<String> filter) {\n        DynamicFileSystem<ByteArray> source = new DynamicFileSystem<>();\n        try (ZipInputStream in = new ZipInputStream(new FileInputStream(file))) {\n            ZipEntry entry;\n\n            while ((entry = in.getNextEntry()) != null) {\n                String name = entry.getName();\n                if (!filter.test(name))\n                    continue;\n\n                if (entry.isDirectory() || name.endsWith(\"\\\\\")) {\n                    source.addFolder(name);\n                } else {\n                    source.addFile(name, ByteArray.copyOf(in.readAllBytes()));\n                }\n            }\n        } catch (IOException | PatternSyntaxException e) {\n            throw new RuntimeException(e);\n        }\n        return source;\n    }\n\n    static final Gson gson = new Gson();\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/LazyFileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * A FileSystem that lazily loads its contents.\n * @param <F>\n */\npublic class LazyFileSystem<F> implements FileSystemImpl<F> {\n\n    private final FileSystem<F> original;\n\n    protected LazyFileSystem(FileSystem<F> original) {\n        this.original = original;\n    }\n\n    private Set<String> folders = null;\n    @Override\n    public Set<String> folders() {\n        if (folders == null) {\n            folders = Set.copyOf(original.folders());\n        }\n        return folders;\n    }\n\n    private Set<String> files = null;\n    @Override\n    public Set<String> files() {\n        if (files == null) {\n            files = Set.copyOf(original.files());\n        }\n        return files;\n    }\n\n\n    private final Map<String, @Nullable FileSystem<F>> folderCache = new ConcurrentHashMap<>();\n    @Override\n    public FileSystem<F> folder(String path) {\n        return folderCache.computeIfAbsent(path, original::folder);\n    }\n\n    private final Map<String, @Nullable F> fileCache = new ConcurrentHashMap<>();\n    @Override\n    public F file(String path) {\n        return fileCache.computeIfAbsent(path, original::file);\n    }\n\n    @Override\n    public String toString() {\n        return FileSystemImpl.toString(this);\n    }\n\n    @Override\n    public FileSystem<F> lazy() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/MappedFileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport java.util.Set;\nimport java.util.function.BiFunction;\n\nclass MappedFileSystem<F, T> implements FileSystemImpl<T> {\n\n    private final FileSystem<F> original;\n    private final BiFunction<String, F, T> mapper;\n\n    protected MappedFileSystem(FileSystem<F> original, BiFunction<String, F, T> mapper) {\n        this.original = original;\n        this.mapper = mapper;\n    }\n\n    @Override\n    public Set<String> folders() {\n        return original.folders();\n    }\n\n    @Override\n    public Set<String> files() {\n        return original.files();\n    }\n\n    @Override\n    public FileSystemImpl<T> folder(String path) {\n        return new MappedFileSystem<>(original.folder(path), mapper);\n    }\n\n    @Override\n    public T file(String path) {\n        return mapper.apply(path, original.file(path));\n    }\n\n    @Override\n    public String toString() {\n        return FileSystemImpl.toString(this);\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/files/PathFileSystem.java",
    "content": "package net.minestom.vanilla.files;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nclass PathFileSystem implements FileSystemImpl<ByteArray> {\n    private final Path path;\n\n    protected PathFileSystem(Path path) {\n        this.path = path;\n    }\n\n    @Override\n    public Set<String> folders() {\n        // Return all folders in the path directory (Only this directory, not subdirectories)\n        try (Stream<Path> paths = Files.walk(this.path, 0)) {\n            return paths\n                    .filter(Files::isDirectory)\n                    .map(path -> path.getFileName().toString())\n                    .collect(Collectors.toUnmodifiableSet());\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public Set<String> files() {\n        // Return all files in the path directory (Only this directory, not subdirectories)\n        try (Stream<Path> paths = Files.walk(this.path, 0)) {\n            return paths\n                    .filter(Files::isRegularFile)\n                    .map(path -> path.getFileName().toString())\n                    .collect(Collectors.toUnmodifiableSet());\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public PathFileSystem folder(String path) {\n        return new PathFileSystem(this.path.resolve(path));\n    }\n\n    @Override\n    public ByteArray file(String path) {\n        try {\n            return ByteArray.wrap(Files.readAllBytes(this.path.resolve(path)));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return FileSystemImpl.toString(this);\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/instance/SetupVanillaInstanceEvent.java",
    "content": "package net.minestom.vanilla.instance;\n\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.instance.Instance;\nimport org.jetbrains.annotations.NotNull;\n\npublic class SetupVanillaInstanceEvent implements InstanceEvent {\n\n    private final Instance instance;\n\n    public SetupVanillaInstanceEvent(@NotNull Instance instance) {\n        this.instance = instance;\n    }\n\n    @Override\n    public @NotNull Instance getInstance() {\n        return instance;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/instance/VanillaExplosion.java",
    "content": "package net.minestom.vanilla.instance;\n\nimport dev.emortal.rayfast.area.Intersection;\nimport dev.emortal.rayfast.area.area3d.Area3d;\nimport dev.emortal.rayfast.casting.grid.GridCast;\nimport dev.emortal.rayfast.vector.Vector3d;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.LivingEntity;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.Explosion;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.batch.AbsoluteBlockBatch;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.utils.time.TimeUnit;\n\nimport java.util.*;\n\npublic class VanillaExplosion extends Explosion {\n\n    public static final String DROP_EVERYTHING_KEY = \"minestom:drop_everything\";\n    public static final String IS_FLAMING_KEY = \"minestom:is_flaming\";\n    public static final String DONT_DESTROY_BLOCKS_KEY = \"minestom:no_block_damage\";\n    private static final Random explosionRNG = new Random();\n\n    private final boolean startsFires;\n    private final boolean dropsEverything;\n\n    public static final String THREAD_POOL_NAME = \"MSVanilla-Explosion\";\n    public static final int THREAD_POOL_COUNT = 2;\n    private final Point center;\n    private final boolean blockDamage;\n\n    protected VanillaExplosion(Point center, float strength, boolean dropEverything, boolean isFlaming, boolean dontDestroyBlocks) {\n        super((float) center.x(), (float) center.y(), (float) center.z(), strength);\n        this.center = center;\n        this.blockDamage = dropEverything;\n        this.startsFires = isFlaming;\n        this.dropsEverything = dontDestroyBlocks;\n    }\n\n    public static Builder builder(Point center, float strength) {\n        return new Builder(center, strength);\n    }\n\n    @Override\n    protected List<Point> prepare(Instance instance) {\n        float maximumBlastRadius = getStrength();\n        Set<Point> positions = new HashSet<>();\n\n        if (blockDamage) {\n            for (int x = 0; x < 16; x++) {\n                for (int y = 0; y < 16; y++) {\n                    for (int z = 0; z < 16; z++) {\n                        if (!(x == 0 || x == 15 || y == 0 || y == 15 || z == 0 || z == 15)) { // must be on outer edge of 16x16x16 cube\n                            continue;\n                        }\n\n                        Vec dir = new Vec(x - 8.5f, y - 8.5f, z - 8.5f).normalize();\n\n                        Iterator<Vector3d> gridIterator = GridCast.createGridIterator(getCenterX(), getCenterY(),\n                                getCenterZ(), dir.x(), dir.y(), dir.z(), 1.0, maximumBlastRadius);\n\n                        double intensity = (0.7f + explosionRNG.nextFloat() * 0.6f) * getStrength();\n\n                        while (gridIterator.hasNext()) {\n                            Vector3d vec = gridIterator.next();\n                            Point pos = new Vec(vec.x(), vec.y(), vec.z());\n\n                            intensity -= 0.225;\n\n                            Block block = Objects.requireNonNull(instance.loadOptionalChunk(pos).join()).getBlock(pos);\n\n                            double explosionResistance = block.registry().explosionResistance();\n                            intensity -= (explosionResistance / 5.0);\n\n                            if (intensity < 0) {\n                                break;\n                            }\n\n                            positions.add(pos);\n                        }\n                    }\n                }\n            }\n        }\n\n        final float damageRadius = maximumBlastRadius; // TODO: should be different from blast radius\n        List<Entity> potentiallyDamagedEntities = getEntitiesAround(instance, damageRadius);\n\n        for (Entity entity : potentiallyDamagedEntities) {\n            affect(entity, damageRadius);\n        }\n\n        if (blockDamage) {\n            for (Point position : positions) {\n                Block block = instance.getBlock(position);\n\n                if (block.isAir()) {\n                    continue;\n                }\n\n//                if (block.compare(Block.TNT)) {\n//                    spawnPrimedTNT(instance, position, new Pos(getCenterX(), getCenterY(), getCenterZ()));\n//                    continue;\n//                }\n\n//                if (customBlock != null) {\n//                    if (!customBlock.onExplode(instance, position, lootTableArguments)) {\n//                        continue;\n//                    }\n//                }\n\n                double p = explosionRNG.nextDouble();\n                boolean shouldDropItem = p <= 1 / getStrength();\n\n                if (dropsEverything || shouldDropItem) {\n//                    LootTableManager lootTableManager = MinecraftServer.getLootTableManager();\n//                    try {\n//                        LootTable table = null;\n//                        if (customBlock != null) {\n//                            table = customBlock.getLootTable(lootTableManager);\n//                        }\n//                        if (table == null) {\n//                            table = lootTableManager.load(Key.key(\"blocks/\" + block.name().toLowerCase()));\n//                        }\n//                        List<ItemStack> output = table.generate(lootTableArguments);\n//                        for (ItemStack out : output) {\n//                            ItemEntity itemEntity = new ItemEntity(out, new Position(position.getX() + explosionRNG.nextFloat(), position.getY() + explosionRNG.nextFloat(), position.getZ() + explosionRNG.nextFloat()));\n//                            itemEntity.setPickupDelay(500L, TimeUnit.MILLISECOND);\n//                            itemEntity.setInstance(instance);\n//                        }\n//                    } catch (FileNotFoundException e) {\n//                        // loot table does not exist, ignore\n//                    }\n                }\n            }\n        }\n\n        return new LinkedList<>(positions);\n    }\n\n//    private void spawnPrimedTNT(Instance instance, Point blockPosition, Point explosionSource) {\n//        Pos initialPosition = new Pos(blockPosition.blockX() + 0.5f, blockPosition.blockY() + 0f, blockPosition.blockZ() + 0.5f);\n//\n//        PrimedTNT primedTNT = new PrimedTNT(10 + (TNTBlockHandler.TNT_RANDOM.nextInt(5) - 2));\n//        primedTNT.setInstance(instance);\n//        primedTNT.teleport(initialPosition);\n//\n//        Point direction = blockPosition.sub(explosionSource);\n//        double distance = explosionSource.distanceSquared(blockPosition);\n//        Vec vec = new Vec(direction.x(), direction.y(), direction.z());\n//        vec = vec.div(distance);\n//\n//        primedTNT.setVelocity(vec.mul(15));\n//    }\n\n    @Override\n    protected void postSend(Instance instance, List<Point> blocks) {\n        if (!startsFires) {\n            return;\n        }\n\n        AbsoluteBlockBatch batch = new AbsoluteBlockBatch();\n\n        for (Point position : blocks) {\n            Block block = instance.getBlock(position);\n\n            if (block.isAir() && position.y() > 0) {\n                if (explosionRNG.nextFloat() < 1 / 3f) {\n\n                    Point belowPos = position.add(0, -1, 0);\n\n                    // check that block below is solid\n                    Block below = instance.getBlock(belowPos);\n\n                    if (below.isSolid()) {\n                        batch.setBlock(position, Block.FIRE);\n                    }\n                }\n            }\n        }\n\n        batch.apply(instance, null);\n    }\n\n    private void affect(Entity e, final float damageRadius) {\n        double exposure = calculateExposure(e, damageRadius);\n        double distance = e.getPosition().distance(center);\n        double impact = (1.0 - distance / damageRadius) * exposure;\n        double damage = Math.floor((impact * impact + impact) * 7 * getStrength() + 1);\n\n        if (e instanceof LivingEntity) {\n            ((LivingEntity) e).damage(DamageType.EXPLOSION, (float) damage);\n        } else {\n            if (e instanceof ItemEntity) {\n                e.scheduleRemove(1L, TimeUnit.SERVER_TICK);\n            }\n            // TODO: different entities will react differently (items despawn, boats, minecarts drop as items, etc.)\n        }\n\n        float blastProtection = 0f; // TODO: apply enchantments\n\n        exposure -= exposure * 0.15f * blastProtection;\n\n        Vec velocityBoost = e.getPosition().asVec().add(0f, e.getEyeHeight(), 0f).sub(center);\n\n        velocityBoost = velocityBoost.normalize().mul(exposure * MinecraftServer.TICK_PER_SECOND);\n        e.setVelocity(e.getVelocity().add(velocityBoost));\n    }\n\n    private float calculateExposure(Entity e, final float damageRadius) {\n        int w = (int) (Math.floor(e.getBoundingBox().width() * 2)) + 1;\n        int h = (int) (Math.floor(e.getBoundingBox().height() * 2)) + 1;\n        int d = (int) (Math.floor(e.getBoundingBox().depth() * 2)) + 1;\n\n        Instance instance = e.getInstance();\n        Pos pos = e.getPosition();\n        double entX = pos.x();\n        double entY = pos.y();\n        double entZ = pos.z();\n\n        // Generate entity hitbox\n        Area3d area3d = Area3d.CONVERTER.from(e);\n\n        int hits = 0;\n        int rays = w * h * d;\n\n        int wd2 = w / 2;\n        int dd2 = d / 2;\n\n        for (int dx = (int) -Math.ceil(wd2); dx < Math.floor(wd2); dx++) {\n            for (int dy = 0; dy < h; dy++) {\n                for (int dz = (int) -Math.ceil(dd2); dz < Math.floor(dd2); dz++) {\n                    double deltaX = entX + dx - getCenterX();\n                    double deltaY = entY + dy - getCenterY();\n                    double deltaZ = entZ + dz - getCenterZ();\n\n                    // TODO: Check for distance\n                    Vector3d intersection = area3d.lineIntersection(getCenterX(), getCenterY(), getCenterZ(),\n                            deltaX, deltaY, deltaZ, Intersection.ANY_3D);\n\n                    if (intersection != null) {\n                        hits++;\n                    }\n                }\n            }\n        }\n\n        return (float) hits / rays;\n    }\n\n    private List<Entity> getEntitiesAround(Instance instance, double damageRadius) {\n        int intRadius = (int) Math.ceil(damageRadius);\n        List<Entity> affected = new LinkedList<>();\n        double radiusSq = damageRadius * damageRadius;\n\n        for (int x = -intRadius; x <= intRadius; x++) {\n            for (int z = -intRadius; z <= intRadius; z++) {\n\n                int posX = (int) Math.floor(getCenterX() + x);\n                int posZ = (int) Math.floor(getCenterZ() + z);\n\n                var list = instance.getChunkEntities(instance.getChunk(posX >> 4, posZ >> 4));\n\n                for (Entity e : list) {\n                    Pos pos = e.getPosition();\n                    double dx = pos.x() - getCenterX();\n                    double dy = pos.y() - getCenterY();\n                    double dz = pos.z() - getCenterZ();\n\n                    if (dx * dx + dy * dy + dz * dz <= radiusSq) {\n                        if (!affected.contains(e)) {\n                            affected.add(e);\n                        }\n                    }\n                }\n            }\n        }\n\n        return affected;\n    }\n\n    public void trigger(Instance instance) {\n        this.apply(instance);\n    }\n\n    public static class Builder {\n\n        private final Point center;\n        private final float strength;\n\n        private boolean dropEverything = true;\n        private boolean isFlaming = false;\n        private boolean dontDestroyBlocks = false;\n\n        protected Builder(Point center, float strength) {\n            this.center = center;\n            this.strength = strength;\n        }\n\n        public Builder dropEverything(boolean dropEverything) {\n            this.dropEverything = dropEverything;\n            return this;\n        }\n\n        public Builder isFlaming(boolean isFlaming) {\n            this.isFlaming = isFlaming;\n            return this;\n        }\n\n        public Builder destroyBlocks(boolean dontDestroyBlocks) {\n            this.dontDestroyBlocks = !dontDestroyBlocks;\n            return this;\n        }\n\n        public VanillaExplosion build() {\n            return new VanillaExplosion(center, strength, dropEverything, isFlaming, dontDestroyBlocks);\n        }\n    }\n}"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/inventory/InventoryManipulation.java",
    "content": "package net.minestom.vanilla.inventory;\n\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.GameMode;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.item.ItemStack;\n\nimport java.util.Objects;\n\npublic class InventoryManipulation {\n    public static void consumeItemIfNotCreative(Player player, ItemStack itemStack, PlayerHand hand) {\n        if (player.getGameMode() == GameMode.CREATIVE) {\n            return;\n        }\n\n        player.setItemInHand(hand, itemStack);\n    }\n\n    /**\n     * If this function returns false, the operation did not complete.\n     *\n     * @return true if there was enough items to consume, false otherwise.\n     */\n    public static boolean consumeItemIfNotCreative(Player player, PlayerHand hand, int amount) {\n        if (player.getGameMode() == GameMode.CREATIVE) {\n            return true;\n        }\n\n        ItemStack item = player.getItemInHand(hand);\n        item = item.withAmount(amt -> amt - amount);\n        if (item.amount() == 0) item = ItemStack.AIR;\n        if (item.amount() < 0) return false;\n        player.setItemInHand(hand, item);\n        return true;\n    }\n\n    public static void damageItemIfNotCreative(Player player, PlayerHand hand, int amount) {\n        if (player.getGameMode() == GameMode.CREATIVE) {\n            return;\n        }\n\n        ItemStack itemStack = player.getItemInHand(hand);\n        int damage = Objects.requireNonNullElse(itemStack.get(DataComponents.DAMAGE), 0);\n        int maxDamage = Objects.requireNonNull(itemStack.material().registry().prototype().get(DataComponents.MAX_DAMAGE));\n        ItemStack newItem = itemStack.with(DataComponents.DAMAGE, damage + amount);\n        if (damage + amount >= maxDamage) {\n            newItem = ItemStack.AIR;\n            // TODO: Item Break Event\n        }\n        player.setItemInHand(hand, newItem);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/Color.java",
    "content": "package net.minestom.vanilla.logging;\n\npublic enum Color {\n    //Color end string, color reset\n    RESET(\"\\033[0m\"),\n\n    // Regular Colors. Normal color, no bold, background color etc.\n    BLACK(\"\\033[0;30m\"),    // BLACK\n    RED(\"\\033[0;31m\"),      // RED\n    GREEN(\"\\033[0;32m\"),    // GREEN\n    YELLOW(\"\\033[0;33m\"),   // YELLOW\n    BLUE(\"\\033[0;34m\"),     // BLUE\n    MAGENTA(\"\\033[0;35m\"),  // MAGENTA\n    CYAN(\"\\033[0;36m\"),     // CYAN\n    WHITE(\"\\033[0;37m\"),    // WHITE\n\n    // Bold\n    BLACK_BOLD(\"\\033[1;30m\"),   // BLACK\n    RED_BOLD(\"\\033[1;31m\"),     // RED\n    GREEN_BOLD(\"\\033[1;32m\"),   // GREEN\n    YELLOW_BOLD(\"\\033[1;33m\"),  // YELLOW\n    BLUE_BOLD(\"\\033[1;34m\"),    // BLUE\n    MAGENTA_BOLD(\"\\033[1;35m\"), // MAGENTA\n    CYAN_BOLD(\"\\033[1;36m\"),    // CYAN\n    WHITE_BOLD(\"\\033[1;37m\"),   // WHITE\n\n    // Underline\n    BLACK_UNDERLINED(\"\\033[4;30m\"),     // BLACK\n    RED_UNDERLINED(\"\\033[4;31m\"),       // RED\n    GREEN_UNDERLINED(\"\\033[4;32m\"),     // GREEN\n    YELLOW_UNDERLINED(\"\\033[4;33m\"),    // YELLOW\n    BLUE_UNDERLINED(\"\\033[4;34m\"),      // BLUE\n    MAGENTA_UNDERLINED(\"\\033[4;35m\"),   // MAGENTA\n    CYAN_UNDERLINED(\"\\033[4;36m\"),      // CYAN\n    WHITE_UNDERLINED(\"\\033[4;37m\"),     // WHITE\n\n    // Background\n    BLACK_BACKGROUND(\"\\033[40m\"),   // BLACK\n    RED_BACKGROUND(\"\\033[41m\"),     // RED\n    GREEN_BACKGROUND(\"\\033[42m\"),   // GREEN\n    YELLOW_BACKGROUND(\"\\033[43m\"),  // YELLOW\n    BLUE_BACKGROUND(\"\\033[44m\"),    // BLUE\n    MAGENTA_BACKGROUND(\"\\033[45m\"), // MAGENTA\n    CYAN_BACKGROUND(\"\\033[46m\"),    // CYAN\n    WHITE_BACKGROUND(\"\\033[47m\"),   // WHITE\n\n    // High Intensity\n    BLACK_BRIGHT(\"\\033[0;90m\"),     // BLACK\n    RED_BRIGHT(\"\\033[0;91m\"),       // RED\n    GREEN_BRIGHT(\"\\033[0;92m\"),     // GREEN\n    YELLOW_BRIGHT(\"\\033[0;93m\"),    // YELLOW\n    BLUE_BRIGHT(\"\\033[0;94m\"),      // BLUE\n    MAGENTA_BRIGHT(\"\\033[0;95m\"),   // MAGENTA\n    CYAN_BRIGHT(\"\\033[0;96m\"),      // CYAN\n    WHITE_BRIGHT(\"\\033[0;97m\"),     // WHITE\n\n    // Bold High Intensity\n    BLACK_BOLD_BRIGHT(\"\\033[1;90m\"),    // BLACK\n    RED_BOLD_BRIGHT(\"\\033[1;91m\"),      // RED\n    GREEN_BOLD_BRIGHT(\"\\033[1;92m\"),    // GREEN\n    YELLOW_BOLD_BRIGHT(\"\\033[1;93m\"),   // YELLOW\n    BLUE_BOLD_BRIGHT(\"\\033[1;94m\"),     // BLUE\n    MAGENTA_BOLD_BRIGHT(\"\\033[1;95m\"),  // MAGENTA\n    CYAN_BOLD_BRIGHT(\"\\033[1;96m\"),     // CYAN\n    WHITE_BOLD_BRIGHT(\"\\033[1;97m\"),    // WHITE\n\n    // High Intensity backgrounds\n    BLACK_BACKGROUND_BRIGHT(\"\\033[0;100m\"),     // BLACK\n    RED_BACKGROUND_BRIGHT(\"\\033[0;101m\"),       // RED\n    GREEN_BACKGROUND_BRIGHT(\"\\033[0;102m\"),     // GREEN\n    YELLOW_BACKGROUND_BRIGHT(\"\\033[0;103m\"),    // YELLOW\n    BLUE_BACKGROUND_BRIGHT(\"\\033[0;104m\"),      // BLUE\n    MAGENTA_BACKGROUND_BRIGHT(\"\\033[0;105m\"),   // MAGENTA\n    CYAN_BACKGROUND_BRIGHT(\"\\033[0;106m\"),      // CYAN\n    WHITE_BACKGROUND_BRIGHT(\"\\033[0;107m\");     // WHITE\n\n    private final String code;\n\n    Color(String code) {\n        this.code = code;\n    }\n\n    @Override\n    public String toString() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/Level.java",
    "content": "package net.minestom.vanilla.logging;\n\npublic enum Level {\n    TRACE, DEBUG, SETUP, INFO, WARN, ERROR\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/Loading.java",
    "content": "package net.minestom.vanilla.logging;\n\npublic interface Loading {\n\n    static void start(String name) {\n        LoadingImpl.CURRENT.waitTask(name);\n    }\n    static StatusUpdater updater() {\n        return LoadingImpl.CURRENT.getUpdater();\n    }\n    static void finish() {\n        LoadingImpl.CURRENT.finishTask();\n    }\n    static void level(Level level) {\n        LoadingImpl.CURRENT.level = level;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/LoadingBar.java",
    "content": "package net.minestom.vanilla.logging;\n\ninterface LoadingBar {\n\n    static LoadingBar console(String initialMessage) {\n        return new LoggingLoadingBar(initialMessage, System.out::print);\n    }\n    static LoadingBar logger(String initialMessage, Logger logger) {\n        return new LoggingLoadingBar(initialMessage, logger::print);\n    }\n    LoadingBar subTask(String task);\n    StatusUpdater updater();\n\n    String message();\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/LoadingImpl.java",
    "content": "package net.minestom.vanilla.logging;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nclass LoadingImpl implements Loading {\n    public static @NotNull LoadingImpl CURRENT = new LoadingImpl(null, null, Level.INFO);\n\n    private final @Nullable LoadingImpl parent;\n    private final @Nullable LoadingBar loadingBar;\n    private final long started = System.currentTimeMillis();\n    private final double progress = 0;\n    public Level level;\n    private LoadingImpl(@Nullable LoadingImpl parent, @Nullable LoadingBar loadingBar, Level level) {\n        this.parent = parent;\n        this.loadingBar = loadingBar;\n        this.level = level;\n    }\n\n    public synchronized void waitTask(String name) {\n        LoadingImpl loading;\n        Logger.logger().level(level).nextLine();\n        if (loadingBar == null) {\n            loading = new LoadingImpl(this, LoadingBar.logger(name, Logger.logger().level(level)), level);\n        } else {\n            loading = new LoadingImpl(this, loadingBar.subTask(name), level);\n        }\n        CURRENT = loading;\n    }\n\n    public synchronized void finishTask() {\n        if (loadingBar == null) {\n            throw new IllegalStateException(\"Cannot finish root task\");\n        }\n        loadingBar.updater().progress(1);\n        assert parent != null;\n        Logger.logger().level(parent.level).printf(\"took %dms%n\", System.currentTimeMillis() - started);\n        CURRENT = this.parent;\n    }\n\n    public synchronized StatusUpdater getUpdater() {\n        if (loadingBar == null)\n            throw new IllegalStateException(\"Cannot get updater for root task\");\n        return loadingBar.updater();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/Logger.java",
    "content": "package net.minestom.vanilla.logging;\n\npublic interface Logger {\n    static Logger logger() {\n        return LoggerImpl.DEFAULT;\n    }\n\n    /**\n     * Debug log entries contain common debug information.\n     */\n    static Logger debug() {\n        return logger().level(Level.DEBUG);\n    }\n    static Logger debug(String message, Object... args) {\n        if (args.length == 0) return debug().println(message);\n        return debug().printf(message, args).println();\n    }\n    static Logger debug(Throwable throwable, Object... args) {\n        return debug().throwable(throwable, args);\n    }\n\n    /**\n     * Setup log entries contain information about the setup of the application.\n     */\n    static Logger setup() {\n        return logger().level(Level.SETUP);\n    }\n    static Logger setup(String message, Object... args) {\n        if (args.length == 0) return setup().println(message);\n        return setup().printf(message, args);\n    }\n    static Logger setup(Throwable throwable, Object... args) {\n        return setup().throwable(throwable, args);\n    }\n\n    /**\n     * Info log entries contain important relevant information.\n     */\n    static Logger info() {\n        return logger().level(Level.INFO);\n    }\n    static Logger info(String message, Object... args) {\n        if (args.length == 0) return info().println(message);\n        return info().printf(message, args).println();\n    }\n    static Logger info(Throwable throwable, Object... args) {\n        return info().throwable(throwable, args);\n    }\n\n    /**\n     * Warn log entries contain technical warnings. Typically, warnings do not prevent the application from continuing.\n     */\n    static Logger warn() {\n        return logger().level(Level.WARN);\n    }\n    static Logger warn(String message, Object... args) {\n        if (args.length == 0) return warn().println(message);\n        return warn().printf(message, args);\n    }\n    static Logger warn(Throwable throwable, Object... args) {\n        return warn().throwable(throwable, args);\n    }\n\n    /**\n     * Error log entries contain technical errors. Errors WILL stop the application from continuing.\n     */\n    static Logger error() {\n        return logger().level(Level.ERROR);\n    }\n    static Logger error(String message, Object... args) {\n        if (args.length == 0) return error().println(message);\n        return error().printf(message, args);\n    }\n    static Logger error(Throwable throwable, Object... args) {\n        return error().throwable(throwable, args);\n    }\n\n    /**\n     * Set the level of the logger\n     * @param level the level\n     * @return the logger\n     */\n    Logger level(Level level);\n\n    /**\n     * Gets the current level of the logger\n     */\n    Level level();\n\n    Logger print(String message);\n    default Logger println(String message) {\n        return print(message).println();\n    }\n    default Logger println() {\n        return print(System.lineSeparator());\n    }\n    Logger printf(String message, Object... args);\n    Logger throwable(Throwable throwable, Object... args);\n\n    /**\n     * Ensures that this logger is ready to print to a blank fresh line.\n     * @return the logger\n     */\n    Logger nextLine();\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/LoggerImpl.java",
    "content": "package net.minestom.vanilla.logging;\n\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.util.Calendar;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\n\nrecord LoggerImpl(Level level) implements Logger {\n\n    public static final Level LOG_LEVEL = Level.valueOf(Objects.requireNonNullElse(System.getenv(\"minestom.vri.log-level\"), \"SETUP\").toUpperCase());\n    static final LoggerImpl DEFAULT = new LoggerImpl(LOG_LEVEL);\n\n    private static LoggerImpl lastLogger = DEFAULT;\n    /** true if this logger implementation was the last used to log a message. false if it was an external call to {@link System#out} */\n    private static boolean loggerWasLast = true;\n    private static boolean newLine = true;\n    private static final Object printLock = new Object();\n\n    private static final PrintStream sysOut = System.out;\n    static {\n        System.setOut(new PrintStream(new OutputStream() {\n            @Override\n            public void write(int b) {\n                synchronized (printLock) {\n                    if (loggerWasLast) {\n                        loggerWasLast = false;\n                        if (!newLine) lastLogger.newLine();\n                    }\n                    sysOut.write(b);\n                }\n            }\n        }, false));\n    }\n\n    @Override\n    public Logger level(Level level) {\n        if (this.level == level) return this;\n        return new LoggerImpl(level);\n    }\n\n    private void consolePrint(String str) {\n        sysOut.print(str);\n        loggerWasLast = true;\n        lastLogger = this;\n    }\n\n    private void newLine() {\n        consolePrint(System.lineSeparator());\n        newLine = true;\n    }\n\n    @Override\n    public Logger print(String message) {\n        synchronized (printLock) {\n            if (LOG_LEVEL.ordinal() > level.ordinal()) return this;\n            if (loggerWasLast && !lastLogger.equals(this) && !newLine) {\n                newLine();\n            }\n            if (newLine) {\n                consolePrint(preparePrefix());\n                newLine = false;\n            }\n            String[] lines = message.split(Pattern.quote(System.lineSeparator()), -1);\n            if (lines.length == 1 && !message.equals(System.lineSeparator())) {\n                printNonNewLine(message);\n                return this;\n            }\n            for (int i = 0; i < lines.length; i++) {\n                String line = lines[i];\n                if (i != 0) newLine();\n                if (!line.isEmpty()) print(line);\n            }\n            return this;\n        }\n    }\n\n    private void printNonNewLine(String message) {\n        if (!message.contains(\"\\r\")) {\n            consolePrint(message);\n            return;\n        }\n\n        String[] split = message.split(Pattern.quote(\"\\r\"), -1);\n        consolePrint(\"\\r\");\n        consolePrint(preparePrefix());\n        consolePrint(split[split.length - 1]);\n    }\n\n    private String preparePrefix() {\n        Calendar date = Calendar.getInstance();\n        int seconds = date.get(Calendar.SECOND);\n        int minutes = date.get(Calendar.MINUTE);\n        int hours = date.get(Calendar.HOUR_OF_DAY);\n        int day = date.get(Calendar.DAY_OF_MONTH);\n        int month = date.get(Calendar.MONTH) + 1;\n        int year = date.get(Calendar.YEAR);\n        return String.format(\"%s[%s/%s/%s %s:%s:%02d]%s %s -> \",\n                Color.GREEN,\n                day, month, year,\n                hours, minutes, seconds,\n                Color.RESET,\n                prepareLevelPrefix());\n    }\n\n    @Override\n    public Logger nextLine() {\n        synchronized (printLock) {\n            if (LOG_LEVEL.ordinal() < level.ordinal()) return this;\n            if (!newLine) newLine();\n            return this;\n        }\n    }\n\n    private String prepareLevelPrefix() {\n        Color color = switch (level) {\n            case TRACE -> Color.WHITE;\n            case DEBUG -> Color.BLUE;\n            case SETUP -> Color.MAGENTA;\n            case INFO -> Color.CYAN;\n            case WARN -> Color.YELLOW;\n            case ERROR -> Color.RED_BOLD_BRIGHT;\n        };\n        return String.format(\"%s(%s)%s\", color, level, Color.RESET);\n    }\n\n    @Override\n    public Logger printf(String message, Object... args) {\n        return print(String.format(message, args));\n    }\n\n    @Override\n    public Logger throwable(Throwable throwable, Object... args) {\n        String info = \"\";\n        if (args.length == 1) {\n            info = \" -> (\" + args[0] + \")\";\n        } else if (args.length > 1) {\n            info = \" -> (\" + String.format(args[0].toString(), args[1]) + \")\";\n        }\n        return print(throwable.getMessage() + info);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) return true;\n        if (!(obj instanceof LoggerImpl other)) return false;\n        return level == other.level;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/LoggingLoadingBar.java",
    "content": "package net.minestom.vanilla.logging;\n\nimport java.util.function.Consumer;\n\nclass LoggingLoadingBar implements LoadingBar {\n\n    private static final double PROGRESS_BAR_WIDTH = Integer.parseInt(System.getProperty(\"vri.loadingBarWidth\", \"20\"));\n    private static final Color MESSAGE_COLOR = Color.valueOf(System.getProperty(\"vri.loadingBarMessageColor\", \"BLUE_BOLD\"));\n\n    private String message;\n    private double progress;\n    private final StatusUpdater updater;\n    private final Consumer<String> out;\n    private final int depth = 0;\n    public LoggingLoadingBar(String initialMessage, Consumer<String> out) {\n        this.message = initialMessage;\n        this.progress = 0;\n        this.updater = new UpdaterImpl();\n        this.out = out;\n        renderThis();\n    }\n\n    @Override\n    public SubTaskLoadingBar subTask(String task) {\n        return new SubTaskLoadingBar(this, task, depth + 1);\n    }\n\n    public StatusUpdater updater() {\n        return updater;\n    }\n\n    @Override\n    public String message() {\n        return message;\n    }\n\n    private void renderThis() {\n        out.accept(\"\\r\");\n        render(message, progress, out);\n    }\n\n    private static void render(String message, double progress, Consumer<String> out) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(MESSAGE_COLOR);\n        sb.append(message);\n        sb.append(\" \");\n        accumulate(progress * PROGRESS_BAR_WIDTH, PROGRESS_BAR_WIDTH, sb);\n        sb.append(\" \");\n        out.accept(sb.toString());\n    }\n\n    @SuppressWarnings(\"UnnecessaryUnicodeEscape\")\n    private static void accumulate(double width, double total, StringBuilder out) {\n        out.append(Color.RESET);\n        out.append(Color.BLUE_BOLD);\n        out.append(\"|\");\n        out.append(Color.RESET);\n        out.append(Color.CYAN);\n        double remaining = total - width;\n        while (width > 1) {\n            width -= 1;\n            out.append(\"=\");\n        }\n        out.append(\">\");\n        out.append(Color.RESET);\n        out.append(Color.WHITE_UNDERLINED);\n        while (remaining > 1) {\n            remaining -= 1;\n            out.append(\" \");\n        }\n        out.append(Color.RESET);\n        out.append(Color.BLUE_BOLD);\n        out.append(\"|\");\n        out.append(Color.RESET);\n    }\n\n    private class UpdaterImpl implements StatusUpdater {\n        @Override\n        public synchronized void progress(double progress) {\n            if (LoggingLoadingBar.this.progress != progress) {\n                LoggingLoadingBar.this.progress = progress;\n                renderThis();\n            }\n        }\n\n        @Override\n        public synchronized void message(String message) {\n            if (!LoggingLoadingBar.this.message.equals(message)) {\n                LoggingLoadingBar.this.message = message;\n                renderThis();\n            }\n        }\n    }\n\n    public class SubTaskLoadingBar implements LoadingBar {\n\n        private final LoadingBar parent;\n        private String message;\n        private double progress;\n        private final UpdaterImpl updater;\n        private final int depth;\n        public SubTaskLoadingBar(LoadingBar parent, String message, int depth) {\n            this.parent = parent;\n            this.message = message;\n            this.progress = 0;\n            this.updater = new UpdaterImpl();\n            this.depth = depth;\n        }\n\n        @Override\n        public LoadingBar subTask(String task) {\n            return new SubTaskLoadingBar(this, task, depth + 1);\n        }\n\n        private void printIndent(LoadingBar bar) {\n            if (bar instanceof SubTaskLoadingBar sub) {\n                printIndent(sub.parent);\n            } else {\n                out.accept(Color.YELLOW_BRIGHT.toString());\n            }\n            out.accept(\"|   \");\n        }\n\n        private void renderThis() {\n            out.accept(\"\\r\");\n            printIndent(parent);\n            out.accept(MESSAGE_COLOR.toString());\n            render(message, progress, out);\n        }\n\n        @Override\n        public StatusUpdater updater() {\n            return updater;\n        }\n\n        @Override\n        public String message() {\n            return message;\n        }\n\n        private class UpdaterImpl implements StatusUpdater {\n            @Override\n            public void progress(double progress) {\n                if (SubTaskLoadingBar.this.progress != progress) {\n                    SubTaskLoadingBar.this.progress = progress;\n                    renderThis();\n                }\n            }\n\n            @Override\n            public void message(String message) {\n                if (!SubTaskLoadingBar.this.message.equals(message)) {\n                    SubTaskLoadingBar.this.message = message;\n                    renderThis();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/SLF4JCompatibilityLayer.java",
    "content": "package net.minestom.vanilla.logging;\n\nimport org.slf4j.Marker;\nimport org.slf4j.helpers.AbstractLogger;\n\nclass SLF4JCompatibilityLayer extends AbstractLogger implements org.slf4j.Logger {\n\n    private final String name;\n    public SLF4JCompatibilityLayer(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public String getName() {\n        return \"[vri] \" + name;\n    }\n\n    @Override\n    public boolean isTraceEnabled() {\n        return Logger.logger().level().ordinal() <= Level.TRACE.ordinal();\n    }\n\n    @Override\n    public boolean isTraceEnabled(Marker marker) {\n        return isTraceEnabled();\n    }\n\n    @Override\n    public boolean isDebugEnabled() {\n        return Logger.logger().level().ordinal() <= Level.DEBUG.ordinal();\n    }\n\n    @Override\n    public boolean isDebugEnabled(Marker marker) {\n        return isDebugEnabled();\n    }\n\n    @Override\n    public boolean isInfoEnabled() {\n        return Logger.logger().level().ordinal() <= Level.INFO.ordinal();\n    }\n\n    @Override\n    public boolean isInfoEnabled(Marker marker) {\n        return isInfoEnabled();\n    }\n\n    @Override\n    public boolean isWarnEnabled() {\n        return Logger.logger().level().ordinal() <= Level.WARN.ordinal();\n    }\n\n    @Override\n    public boolean isWarnEnabled(Marker marker) {\n        return isWarnEnabled();\n    }\n\n    @Override\n    public boolean isErrorEnabled() {\n        return Logger.logger().level().ordinal() <= Level.ERROR.ordinal();\n    }\n\n    @Override\n    public boolean isErrorEnabled(Marker marker) {\n        return isErrorEnabled();\n    }\n\n    @Override\n    protected String getFullyQualifiedCallerName() {\n        return null;\n    }\n\n    private Level fromSlf4jLevel(org.slf4j.event.Level level) {\n        return switch (level) {\n            case TRACE -> Level.TRACE;\n            case DEBUG -> Level.DEBUG;\n            case INFO -> Level.INFO;\n            case WARN -> Level.WARN;\n            case ERROR -> Level.ERROR;\n        };\n    }\n\n    @Override\n    protected void handleNormalizedLoggingCall(org.slf4j.event.Level level, Marker marker, String s, Object[] objects, Throwable throwable) {\n        // TODO: Marker support?\n        Level minestomLevel = fromSlf4jLevel(level);\n        String message = org.slf4j.helpers.MessageFormatter.arrayFormat(s, objects).getMessage();\n        Logger.logger().level(minestomLevel).println(message);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/SLF4JServiceProvider.java",
    "content": "package net.minestom.vanilla.logging;\n\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.IMarkerFactory;\nimport org.slf4j.helpers.BasicMarkerFactory;\nimport org.slf4j.helpers.NOPMDCAdapter;\nimport org.slf4j.spi.MDCAdapter;\n\npublic class SLF4JServiceProvider implements  org.slf4j.spi.SLF4JServiceProvider {\n\n    public SLF4JServiceProvider() {\n    }\n\n    @Override\n    public ILoggerFactory getLoggerFactory() {\n        return SLF4JCompatibilityLayer::new;\n    }\n\n    private final IMarkerFactory markerFactory = new BasicMarkerFactory();\n    private final MDCAdapter mdcAdapter = new NOPMDCAdapter();\n\n    @Override\n    public IMarkerFactory getMarkerFactory() {\n        return markerFactory;\n    }\n\n    @Override\n    public MDCAdapter getMDCAdapter() {\n        return mdcAdapter;\n    }\n\n    @Override\n    public String getRequestedApiVersion() {\n        return \"2.0\";\n    }\n\n    @Override\n    public void initialize() {\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/logging/StatusUpdater.java",
    "content": "package net.minestom.vanilla.logging;\n\npublic interface StatusUpdater {\n    /**\n     * Updates the progress bar without changing the text message.\n     * @param progress the progress, between 0 and 1\n     */\n    void progress(double progress);\n\n    /**\n     * Updates the text message without changing the progress bar.\n     * @param message the new message\n     */\n    void message(String message);\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/EnderChestSystem.java",
    "content": "package net.minestom.vanilla.system;\n\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class EnderChestSystem {\n\n    public static final EnderChestSystem INSTANCE = new EnderChestSystem();\n\n    private final Map<UUID, List<ItemStack>> itemsMap = new HashMap<>();\n\n    private EnderChestSystem() {\n    }\n\n    public List<ItemStack> getItems(@NotNull Player player) {\n        return getItems(player.getUuid());\n    }\n\n    public @NotNull List<ItemStack> getItems(@NotNull UUID uuid) {\n        return itemsMap.computeIfAbsent(uuid, k -> List.of());\n    }\n\n    public static EnderChestSystem getInstance() {\n        return INSTANCE;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/NetherPortal.java",
    "content": "package net.minestom.vanilla.system;\n\nimport it.unimi.dsi.fastutil.objects.ObjectArrayList;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.instance.Chunk;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.network.packet.server.play.ParticlePacket;\nimport net.minestom.server.network.packet.server.play.WorldEventPacket;\nimport net.minestom.server.particle.Particle;\nimport net.minestom.server.worldevent.WorldEvent;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.LinkedBlockingDeque;\n\n/**\n * Every useful method linked to Nether portals goes here\n * TODO: Could be repurposed to create custom portals\n *\n * @author jglrxavpok\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic final class NetherPortal {\n\n    private static final int MINIMUM_WIDTH = 2;\n    private static final int MINIMUM_HEIGHT = 3;\n    private static final int MAXIMUM_HEIGHT = 22;\n    private static final int MAXIMUM_WIDTH = 22;\n\n    private static long nextID = 0;\n    private static final Map<Long, NetherPortal> portalsById = new HashMap<>();\n\n    /**\n     * Only NORTH and WEST are valid\n     */\n    private final Axis axis;\n    private final Point frameTopLeftCorner;\n    private final Point frameBottomRightCorner;\n    private final Vec averagePosition;\n    private final long id;\n\n    /**\n     * Prevents considering this portal as non-valid during generation (otherwise portals may try to break themselves when\n     * they are being placed due to neighbor updates of portal blocks)\n     */\n    private boolean generating;\n\n    public static final NetherPortal NONE = new NetherPortal(Axis.X, new Pos(0, -1, 0), new Pos(0, -1, 0));\n\n    public static @Nullable NetherPortal fromId(@Nullable Long id) {\n        return portalsById.get(id);\n    }\n\n    public NetherPortal(Axis axis, Point frameBottomRightCorner, Point frameTopLeftCorner) {\n        this.axis = axis;\n        this.frameBottomRightCorner = frameBottomRightCorner;\n        this.frameTopLeftCorner = frameTopLeftCorner;\n        this.averagePosition = new Vec(\n                (frameBottomRightCorner.x() + frameTopLeftCorner.x()) / 2D,\n                (frameBottomRightCorner.y() + frameTopLeftCorner.y()) / 2D,\n                (frameBottomRightCorner.z() + frameTopLeftCorner.z()) / 2D\n        );\n\n        this.id = nextID++;\n        portalsById.put(this.id, this);\n    }\n\n    public Axis getAxis() {\n        return axis;\n    }\n\n    public Point getFrameBottomRightCorner() {\n        return frameBottomRightCorner;\n    }\n\n    public Point getFrameTopLeftCorner() {\n        return frameTopLeftCorner;\n    }\n\n    /**\n     * Position of the center of the frame\n     * Used to compute the closest portal when linking portals\n     */\n    public Vec getCenter() {\n        return averagePosition;\n    }\n\n    public boolean isStillValid(Instance instance) {\n        return generating || checkFrameIsObsidian(instance, axis, frameBottomRightCorner, frameTopLeftCorner);\n    }\n\n    public void breakFrame(Instance instance) {\n        Set<Point> blockPositions = new HashSet<>();\n        replaceFrameContents(instance, true, Block.AIR, blockPositions);\n\n        for (Point pos : blockPositions) {\n            // play break animation for each portal block\n            int x = pos.blockX();\n            int y = pos.blockY();\n            int z = pos.blockZ();\n\n            ParticlePacket particlePacket = new ParticlePacket(\n                    Particle.BLOCK.withBlock(Block.NETHER_PORTAL),\n                    x + 0.5f, y, z + 0.5f,\n                    0.4f, 0.5f, 0.4f,\n                    0.3f, 10\n            );\n\n            WorldEventPacket effectPacket = new WorldEventPacket(\n                    WorldEvent.PARTICLES_DESTROY_BLOCK.id(),\n                    pos,\n                    Block.NETHER_PORTAL.id(),\n                    false\n            );\n\n            Chunk chunk = instance.getChunkAt(pos);\n            if (chunk != null) {\n                chunk.sendPacketToViewers(particlePacket);\n                chunk.sendPacketToViewers(effectPacket);\n            }\n        }\n    }\n\n    public boolean tryFillFrame(Instance instance) {\n        if (instance.getDimensionType().key().equals(Key.key(\"the_end\"))) {\n            return false;\n        }\n\n        // Block to use\n        Block block = Block.NETHER_PORTAL;// .withTag(NetherPortalBlockHandler.RELATED_PORTAL_KEY, this.id());\n\n        return replaceFrameContents(instance, true, block, null);\n    }\n\n    /**\n     * @param instance            the instance to build the frame\n     * @param checkPreviousBlocks should check if frame is full of air/portal/fire\n     * @param block               the block to place\n     * @param blockPositions      the set to fill with block positions\n     */\n    private boolean replaceFrameContents(Instance instance, boolean checkPreviousBlocks, Block block, @Nullable Set<Point> blockPositions) {\n        int minX = Math.min(frameTopLeftCorner.blockX(), frameBottomRightCorner.blockX());\n        int minY = Math.min(frameBottomRightCorner.blockY(), frameTopLeftCorner.blockY());\n        int minZ = Math.min(frameTopLeftCorner.blockZ(), frameBottomRightCorner.blockZ());\n\n        int maxX = Math.max(frameTopLeftCorner.blockX(), frameBottomRightCorner.blockX());\n        int maxY = Math.max(frameBottomRightCorner.blockY(), frameTopLeftCorner.blockY());\n        int maxZ = Math.max(frameTopLeftCorner.blockZ(), frameBottomRightCorner.blockZ());\n\n        int width = computeWidth() - 1; // encompasses frame blocks\n\n        if (checkPreviousBlocks) {\n            if (!checkInsideFrameForAir(instance, minX, maxX, minY, maxY, minZ, maxZ, axis)) {\n                return false;\n            }\n        }\n\n        // Fill portal\n        int xMul = axis.xMultiplier;\n        int zMul = axis.zMultiplier;\n\n        for (int d = 1; d < width; d++) {\n            for (int y = minY + 1; y <= maxY - 1; y++) {\n                int x = minX + (d * xMul);\n                int z = minZ + (d * zMul);\n\n                instance.setBlock(x, y, z, block);\n\n                if (blockPositions != null) {\n                    blockPositions.add(new Pos(x, y, z));\n                }\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Gets a {@link NetherPortal} frame description from a block that would be contained inside the frame.\n     *\n     * @param instance the instance to draw blocks from\n     * @param pos      the position of the potential future frame block\n     * @return null if no valid frame was found, a new {@link NetherPortal} instance with detailed info otherwise\n     */\n    public static NetherPortal findPortalFrameFromFrameBlock(Instance instance, Point pos) {\n        NetherPortal alongAxisX = findPortalFrameFromFrameBlock(instance, pos, Axis.X);\n        if (alongAxisX != null)\n            return alongAxisX;\n        return findPortalFrameFromFrameBlock(instance, pos, Axis.Z);\n    }\n\n\n    private static NetherPortal findPortalFrameFromFrameBlock(Instance instance, Point frameBlock, Axis axis) {\n        List<Point> insideFrame = new LinkedList<>();\n        List<Point> considered = new LinkedList<>();\n        Queue<Point> neighbors = new LinkedBlockingDeque<>();\n        neighbors.add(frameBlock);\n\n        while (!neighbors.isEmpty()) {\n            Point position = neighbors.poll();\n            considered.add(position);\n\n            int xDistance = Math.abs(position.blockX() - frameBlock.blockX());\n            int zDistance = Math.abs(position.blockZ() - frameBlock.blockZ());\n\n            int height = Math.abs(position.blockY() - frameBlock.blockY());\n            int width = xDistance * axis.xMultiplier + zDistance * axis.zMultiplier;\n            if (width >= MAXIMUM_WIDTH) {\n                continue;\n            }\n\n            if (height >= MAXIMUM_HEIGHT) {\n                continue;\n            }\n\n            Block block = instance.getBlock(position);\n\n            if (!block.isAir() &&\n                    block != Block.FIRE &&\n                    block != Block.NETHER_PORTAL\n            ) {\n                continue;\n            }\n\n            insideFrame.add(position);\n\n            Point above = position.add(0, +1, 0);\n            Point below = position.add(0, -1, 0);\n            Point left = position.add(-1 * axis.xMultiplier, 0, -1 * axis.zMultiplier);\n            Point right = position.add(axis.xMultiplier, 0, axis.zMultiplier);\n\n            if (!considered.contains(above) && !neighbors.contains(above)) {\n                neighbors.add(above);\n            }\n\n            if (!considered.contains(below) && !neighbors.contains(below)) {\n                neighbors.add(below);\n            }\n\n            if (!considered.contains(left) && !neighbors.contains(left)) {\n                neighbors.add(left);\n            }\n\n            if (!considered.contains(right) && !neighbors.contains(right)) {\n                neighbors.add(right);\n            }\n        }\n\n        // check that insideFrame represent a rectangle full of air\n        int minX = Integer.MAX_VALUE;\n        int maxX = Integer.MIN_VALUE;\n        int minY = Integer.MAX_VALUE;\n        int maxY = Integer.MIN_VALUE;\n        int minZ = Integer.MAX_VALUE;\n        int maxZ = Integer.MIN_VALUE;\n\n        for (Point framePosition : insideFrame) {\n            int x = framePosition.blockX();\n            int y = framePosition.blockY();\n            int z = framePosition.blockZ();\n\n            if (x < minX) minX = x;\n            if (y < minY) minY = y;\n            if (z < minZ) minZ = z;\n            if (x > maxX) maxX = x;\n            if (y > maxY) maxY = y;\n            if (z > maxZ) maxZ = z;\n        }\n\n        boolean isRectangleOfAir = checkInsideFrameForAir(instance, minX, maxX, minY, maxY, minZ, maxZ, axis);\n        if (!isRectangleOfAir) {\n            return null;\n        }\n\n        int width = (maxX - minX) * axis.xMultiplier + (maxZ - minZ) * axis.zMultiplier + 1; // does not encompass frame\n        int height = maxY - minY + 1;\n\n        if (width < MINIMUM_WIDTH) { // too narrow\n            return null;\n        }\n\n        if (height < MINIMUM_HEIGHT) { // too small\n            return null;\n        }\n\n        Pos bottomRight = null;\n        Pos topLeft = null;\n        switch (axis) {\n            case X -> {\n                bottomRight = new Pos(maxX + 1, minY - 1, minZ);\n                topLeft = new Pos(minX - 1, maxY + 1, minZ);\n            }\n            case Z -> {\n                bottomRight = new Pos(minX, minY - 1, maxZ + 1);\n                topLeft = new Pos(minX, maxY + 1, minZ - 1);\n            }\n        }\n\n        // TODO: check that frame is obsidian\n        if (!checkFrameIsObsidian(instance, axis, bottomRight, topLeft)) {\n            return null;\n        }\n\n        return new NetherPortal(axis, bottomRight, topLeft);\n    }\n\n    private static boolean checkFrameIsObsidian(Instance instance, Axis axis, Point bottomRightCorner, Point topLeftCorner) {\n        int minX = Math.min(topLeftCorner.blockX(), bottomRightCorner.blockX());\n        int minY = bottomRightCorner.blockY();\n        int minZ = Math.min(topLeftCorner.blockZ(), bottomRightCorner.blockZ());\n\n        int maxX = Math.max(topLeftCorner.blockX(), bottomRightCorner.blockX());\n        int maxY = topLeftCorner.blockY();\n        int maxZ = Math.max(topLeftCorner.blockZ(), bottomRightCorner.blockZ());\n\n        int width = (maxX - minX) * axis.xMultiplier + (maxZ - minZ) * axis.zMultiplier + 1; // encompasses frame blocks\n        int height = maxY - minY + 1;\n\n        // offsets by one are used to ignore portal corners\n\n        // top and bottom\n        for (int i = 1; i < width - 1; i++) {\n            int x = minX;\n            int z = minZ;\n            if (axis == Axis.X) {\n                x += i;\n            } else {\n                z += i;\n            }\n\n            // bottom\n            Block frameBlock = instance.getBlock(x, minY, z);\n            if (!frameBlock.compare(Block.OBSIDIAN)) {\n                return false;\n            }\n\n            // top\n            frameBlock = instance.getBlock(x, maxY, z);\n            if (!frameBlock.compare(Block.OBSIDIAN)) {\n                return false;\n            }\n        }\n\n        // left and right\n        for (int j = 1; j < height - 1; j++) {\n            int x = minX;\n            int z = minZ;\n\n            // left\n            Block frameBlock = instance.getBlock(x, minY + j, z);\n            if (!frameBlock.compare(Block.OBSIDIAN)) {\n                return false;\n            }\n\n            if (axis == Axis.X) {\n                x += width - 1;\n            } else {\n                z += width - 1;\n            }\n\n            // right\n            frameBlock = instance.getBlock(x, minY + j, z);\n            if (!frameBlock.compare(Block.OBSIDIAN)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n\n    private static boolean checkInsideFrameForAir(Instance instance, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, Axis axis) {\n        int width = (maxX - minX) * axis.xMultiplier + (maxZ - minZ) * axis.zMultiplier;\n\n        for (int i = 1; i <= width - 1; i++) {\n            for (int y = minY + 1; y <= maxY - 1; y++) {\n                int x = minX;\n                int z = minZ;\n                if (axis == Axis.X) {\n                    x += i;\n                } else {\n                    z += i;\n                }\n                Block currentBlock = instance.getBlock(x, y, z);\n                if (!currentBlock.isAir() &&\n                        (!currentBlock.compare(Block.FIRE)) &&\n                        (!currentBlock.compare(Block.NETHER_PORTAL))\n                ) {\n                    return false;\n                }\n            }\n        }\n        return true;\n    }\n\n    public void unregister(Instance instance) {\n//        instance.setTag(Utils.);\n//        if (instance.getData() != null) {\n//            Data data = instance.getData();\n//            NetherPortalList list = data.getOrDefault(LIST_KEY, null);\n//            if(list != null) {\n//                list.remove(this);\n//            }\n//        }\n    }\n\n    public void register(Instance instance) {\n//        if (instance.getData() != null) {\n//            Data data = instance.getData();\n//            NetherPortalList list = data.getOrDefault(LIST_KEY, null);\n//            if(list == null) {\n//                NetherPortalList newList = new NetherPortalList();\n//                data.set(LIST_KEY, newList, NetherPortalList.class);\n//                list = newList;\n//            }\n//\n//            list.add(this);\n//        }\n    }\n\n    public void generate(Instance instance) {\n        generating = true;\n        // NetherPortalBlockHandler portalBlock = (NetherPortalBlockHandler) Block.NETHER_PORTAL.handler();\n\n        loadAround(instance, frameTopLeftCorner).join();\n        loadAround(instance, frameBottomRightCorner).join();\n\n        createFrame(instance);\n\n        Block block = Block.NETHER_PORTAL; // .withTag(NetherPortalBlockHandler.RELATED_PORTAL_KEY, this.id());\n\n        replaceFrameContents(instance, false, block, null);\n\n        register(instance);\n        generating = false;\n    }\n\n    /**\n     * Ensure chunks around the portal corner are loaded (3x3 area centered on chunk containing frame corner)\n     */\n    private CompletableFuture<Void> loadAround(Instance instance, Point corner) {\n        int chunkX = corner.blockX() >> 4;\n        int chunkZ = corner.blockZ() >> 4;\n\n        ObjectArrayList<CompletableFuture<Chunk>> futures = new ObjectArrayList<>();\n\n        for (int x = -1; x <= 1; x++) {\n            for (int z = -1; z <= 1; z++) {\n                futures.add(instance.loadChunk(chunkX + x, chunkZ + z));\n            }\n        }\n\n        return CompletableFuture.allOf(futures.elements());\n    }\n\n    private void createFrame(Instance instance) {\n        int minX = Math.min(frameTopLeftCorner.blockX(), frameBottomRightCorner.blockX());\n        int minY = frameBottomRightCorner.blockY();\n        int minZ = Math.min(frameTopLeftCorner.blockZ(), frameBottomRightCorner.blockZ());\n\n        int maxY = frameTopLeftCorner.blockY();\n\n        int width = computeWidth(); // encompasses frame blocks\n        int height = computeHeight();\n\n        // top and bottom\n        for (int i = 0; i < width; i++) {\n            int x = minX;\n            int z = minZ;\n            if (axis == Axis.X) {\n                x += i;\n            } else {\n                z += i;\n            }\n\n            // bottom\n            instance.setBlock(x, minY, z, Block.OBSIDIAN);\n\n            // top\n            instance.setBlock(x, maxY, z, Block.OBSIDIAN);\n        }\n\n        // left and right\n        for (int j = 0; j < height; j++) {\n            int x = minX;\n            int z = minZ;\n\n            // left\n            instance.setBlock(x, minY + j, z, Block.OBSIDIAN);\n\n            if (axis == Axis.X) {\n                x += width - 1;\n            } else {\n                z += width - 1;\n            }\n\n            // right\n            instance.setBlock(x, minY + j, z, Block.OBSIDIAN);\n        }\n    }\n\n    public int computeWidth() {\n        int minX = Math.min(frameTopLeftCorner.blockX(), frameBottomRightCorner.blockX());\n        int minZ = Math.min(frameTopLeftCorner.blockZ(), frameBottomRightCorner.blockZ());\n\n        int maxX = Math.max(frameTopLeftCorner.blockX(), frameBottomRightCorner.blockX());\n        int maxZ = Math.max(frameTopLeftCorner.blockZ(), frameBottomRightCorner.blockZ());\n        return (maxX - minX) * axis.xMultiplier + (maxZ - minZ) * axis.zMultiplier + 1;\n    }\n\n    public int computeHeight() {\n        int minY = frameBottomRightCorner.blockY();\n        int maxY = frameTopLeftCorner.blockY();\n        return maxY - minY + 1;\n    }\n\n    public long id() {\n        return this.id;\n    }\n\n    public enum Axis {\n        X(1, 0),\n        Z(0, 1);\n\n        public final int xMultiplier;\n        public final int zMultiplier;\n\n        Axis(int xMultiplier, int zMultiplier) {\n            this.xMultiplier = xMultiplier;\n            this.zMultiplier = zMultiplier;\n        }\n\n\n        @Override\n        public String toString() {\n            return name().toLowerCase();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/RayFastManager.java",
    "content": "package net.minestom.vanilla.system;\n\nimport dev.emortal.rayfast.area.area3d.Area3d;\nimport dev.emortal.rayfast.area.area3d.Area3dRectangularPrism;\nimport net.minestom.server.entity.Entity;\n\npublic class RayFastManager {\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static void init() {\n        Area3d.CONVERTER.register(Entity.class, entity ->\n                Area3dRectangularPrism.wrapper(\n                        entity,\n                        ignored -> entity.getBoundingBox().minX() + entity.getPosition().x(),\n                        ignored -> entity.getBoundingBox().minY() + entity.getPosition().y(),\n                        ignored -> entity.getBoundingBox().minZ() + entity.getPosition().z(),\n                        ignored -> entity.getBoundingBox().maxX() + entity.getPosition().x(),\n                        ignored -> entity.getBoundingBox().maxY() + entity.getPosition().y(),\n                        ignored -> entity.getBoundingBox().maxZ() + entity.getPosition().z()\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/ServerProperties.java",
    "content": "package net.minestom.vanilla.system;\n\nimport java.io.*;\nimport java.util.Objects;\nimport java.util.Properties;\n\n/**\n * Helper class to load and save the contents of the server.properties file\n */\npublic class ServerProperties {\n\n    private final File source;\n    private final Properties properties;\n\n    /**\n     * Creates a new property list from a given file. Will attempt to create the file and fill with defaults if it does not exist\n     */\n    public ServerProperties(File source) throws IOException {\n        properties = new Properties();\n        loadDefault();\n        this.source = source;\n        if (source.exists()) {\n            load();\n        } else {\n            save(); // write defaults to file\n        }\n    }\n\n    public ServerProperties(String source) throws IOException {\n        properties = new Properties();\n        properties.load(new StringReader(source));\n        this.source = null;\n\n    }\n\n    private void loadDefault() throws IOException {\n        try (var defaultInput = new InputStreamReader(Objects.requireNonNull(ServerProperties.class.getResourceAsStream(\"/server.properties.default\")))) {\n            properties.load(defaultInput);\n        }\n    }\n\n    public void load() throws IOException {\n        try (var reader = new FileReader(source)) {\n            properties.load(reader);\n        }\n    }\n\n    public String get(String key) {\n        return properties.getProperty(key);\n    }\n\n    public void set(String key, String value) {\n        properties.put(key, value);\n    }\n\n    public void save() throws IOException {\n        try (var writer = new FileWriter(source)) {\n            properties.store(writer, \"Minestom server properties\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/nether/EntityEnterNetherPortalEvent.java",
    "content": "package net.minestom.vanilla.system.nether;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.EntityEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.vanilla.system.NetherPortal;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Called when a entity starts colliding with a nether portal\n */\npublic class EntityEnterNetherPortalEvent implements Event, InstanceEvent, EntityEvent {\n\n    private final Entity entity;\n    private final Point position;\n    private final NetherPortal portal;\n\n    public EntityEnterNetherPortalEvent(Entity entity, Point position, NetherPortal portal) {\n        this.entity = entity;\n        this.position = position;\n        this.portal = portal;\n    }\n\n    @Override\n    public @NotNull Entity getEntity() {\n        return entity;\n    }\n\n    /**\n     * Position of the portal block which triggered the update\n     *\n     * @return\n     */\n    public Point getPosition() {\n        return position;\n    }\n\n    /**\n     * The nether portal the entity is in. Can be null if the portal was added with /setblock\n     *\n     * @return\n     */\n    public NetherPortal getPortal() {\n        return portal;\n    }\n\n    @Override\n    public Instance getInstance() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/nether/NetherPortalTeleportEvent.java",
    "content": "package net.minestom.vanilla.system.nether;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.CancellableEvent;\nimport net.minestom.server.event.trait.EntityEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.vanilla.system.NetherPortal;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Triggered when a nether portal attempts to teleport entities between dimensions\n */\npublic class NetherPortalTeleportEvent implements Event, CancellableEvent, EntityEvent {\n\n    private boolean cancelled = false;\n    private final Entity entity;\n    private final Point portalBlockPosition;\n    private final NetherPortal portal;\n    private final long ticksSpentInPortal;\n    private Instance targetInstance;\n    private Point targetPosition;\n    private NetherPortal targetPortal;\n    private boolean createsNewPortal;\n\n    public NetherPortalTeleportEvent(\n            Entity entity,\n            Point portalBlockPosition,\n            NetherPortal portal,\n            long ticksSpentInPortal,\n            Instance targetInstance,\n            Point targetPosition,\n            NetherPortal targetPortal,\n            boolean createsNewPortal\n    ) {\n        this.entity = entity;\n        this.portalBlockPosition = portalBlockPosition;\n        this.portal = portal;\n        this.ticksSpentInPortal = ticksSpentInPortal;\n        this.targetInstance = targetInstance;\n        this.targetPosition = targetPosition;\n        this.targetPortal = targetPortal;\n        this.createsNewPortal = createsNewPortal;\n    }\n\n    /**\n     * Teleporting entity\n     */\n    public @NotNull Entity getEntity() {\n        return entity;\n    }\n\n    /**\n     * Position of the portal block which triggered the teleportation\n     */\n    public Point getPortalBlockPosition() {\n        return portalBlockPosition;\n    }\n\n    /**\n     * CAN BE NULL. The Nether portal trying to teleport an entity. Can be null if the portal block is not part of a nether portal frame\n     * (for instance, placed with /setblock)\n     */\n    public NetherPortal getPortal() {\n        return portal;\n    }\n\n    /**\n     * Number of ticks the entity spent in portal before this event\n     */\n    public long getTicksSpentInPortal() {\n        return ticksSpentInPortal;\n    }\n\n    /**\n     * Instance to teleport the entity to\n     */\n    public Instance getTargetInstance() {\n        return targetInstance;\n    }\n\n    /**\n     * Instance to teleport the entity to\n     */\n    public void setTargetDimension(Instance targetInstance) {\n        this.targetInstance = targetInstance;\n    }\n\n    /**\n     * Position to teleport the entity to. Set to the center of the linked portal, if available\n     */\n    public Point getTargetPosition() {\n        return targetPosition;\n    }\n\n    /**\n     * Position to teleport the entity to. Set by default to the center of the linked portal, if available\n     */\n    public void setTargetPosition(Point targetPosition) {\n        this.targetPosition = targetPosition;\n    }\n\n    /**\n     * The linked Nether Portal to teleport to, if any\n     */\n    public NetherPortal getTargetPortal() {\n        return targetPortal;\n    }\n\n    /**\n     * Set the portal to teleport to. Warning: the position to teleport the entity to is defined by {@link #getTargetPosition()}\n     */\n    public void setTargetPortal(NetherPortal targetPortal) {\n        this.targetPortal = targetPortal;\n    }\n\n    /**\n     * Should the teleportation create a new portal on the other side?\n     */\n    public boolean createsNewPortal() {\n        return createsNewPortal;\n    }\n\n    /**\n     * @see #createsNewPortal\n     */\n    public void createsNewPortal(boolean createNewPortal) {\n        this.createsNewPortal = createNewPortal;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancel) {\n        cancelled = cancel;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/system/nether/NetherPortalUpdateEvent.java",
    "content": "package net.minestom.vanilla.system.nether;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.trait.EntityEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.vanilla.system.NetherPortal;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Called when a nether portal updates the counter inside an entity\n */\npublic class NetherPortalUpdateEvent implements Event, EntityEvent, InstanceEvent {\n\n    private final Entity entity;\n    private final Point position;\n    private final NetherPortal portal;\n    private final Instance instance;\n    private final long tickSpentInPortal;\n\n    public NetherPortalUpdateEvent(Entity entity, Point position, NetherPortal portal, Instance instance, long tickSpentInPortal) {\n        this.entity = entity;\n        this.position = position;\n        this.portal = portal;\n        this.instance = instance;\n        this.tickSpentInPortal = tickSpentInPortal;\n    }\n\n    /**\n     * Amount of time spent inside this portal\n     */\n    public long getTickSpentInPortal() {\n        return tickSpentInPortal;\n    }\n\n    /**\n     * Position of the portal block which triggered the update\n     */\n    public Point getPosition() {\n        return position;\n    }\n\n    /**\n     * The nether portal the entity is in. Can be null if the portal was added with /setblock\n     */\n    public NetherPortal getPortal() {\n        return portal;\n    }\n\n    @Override\n    public @NotNull Entity getEntity() {\n        return entity;\n    }\n\n    @Override\n    public @NotNull Instance getInstance() {\n        return instance;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/tag/Tags.java",
    "content": "package net.minestom.vanilla.tag;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.*;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.tag.Tag;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\npublic interface Tags {\n\n\n    interface Items {\n        Tag<BinaryTag> TAG = Tag.NBT(\"tag\")\n                .defaultValue(CompoundBinaryTag.empty());\n        Tag<CompoundBinaryTag> BLOCKSTATE = TAG.path(\"BlockEntityTag\")\n                .map(nbt -> nbt instanceof CompoundBinaryTag compound ? compound : CompoundBinaryTag.empty(), nbt -> nbt)\n                .defaultValue(CompoundBinaryTag.empty());\n\n        interface Banner {\n            record Pattern(String pattern, int color) {\n            }\n\n            Tag<List<Pattern>> PATTERNS = BLOCKSTATE.path(\"Patterns\") // TODO: Is this correct?\n                    .map(nbt -> new Pattern(\n                            nbt.getString(\"Pattern\"),\n                            nbt.getInt(\"Color\")\n                    ), pattern -> CompoundBinaryTag.from(Map.of(\n                            \"Pattern\", StringBinaryTag.stringBinaryTag(pattern.pattern()),\n                            \"Color\", IntBinaryTag.intBinaryTag(pattern.color())\n                    )))\n                    .list();\n        }\n\n        interface Potion {\n            Tag<Key> POTION = TAG.path(\"Potion\")\n                    .map(nbt -> nbt instanceof StringBinaryTag nbts ?\n                            Key.key(nbts.value()) : Key.key(\"minecraft:empty\"),\n                            nbt -> StringBinaryTag.stringBinaryTag(nbt.toString()));\n        }\n    }\n\n    interface Blocks {\n        interface Campfire {\n            Tag<List<ItemStack>> ITEMS = Tag.NBT(\"Items\")\n                    .map(nbt -> nbt instanceof CompoundBinaryTag compound ? compound : CompoundBinaryTag.empty(), nbt -> nbt)\n                    .list()\n                    .map(nbtList -> nbtList.stream()\n                                    .map(nbt -> ItemStack.of(Material.fromKey(nbt.getString(\"id\"))))\n                                    .collect(Collectors.toList()),\n                            items -> IntStream.range(0, items.size())\n                                    .mapToObj(slot -> CompoundBinaryTag.from(Map.of(\n                                            \"id\", StringBinaryTag.stringBinaryTag(items.get(slot).material().name()),\n                                            \"Slot\", ByteBinaryTag.byteBinaryTag((byte) slot),\n                                            \"Count\", ByteBinaryTag.byteBinaryTag((byte) 1)\n                                    )))\n                                    .toList());\n\n            // The number of ticks that the campfire has been cooking for, for each of the 4 slots, 0 means end of the cooking\n            Tag<List<Integer>> COOKING_PROGRESS = Tag.Integer(\"vri:campfire:cooking_progress\").list().defaultValue(List.of(0, 0, 0, 0));\n\n        }\n        interface Smelting {\n            // The number of ticks that the furnace can cook for without using another fuel item\n            Tag<Integer> COOKING_TICKS = Tag.Integer(\"vri:furnace:cooking_ticks\").defaultValue(0);\n\n            // The last fuel item that was used. Null if this furnace is not currently burning\n            Tag<Material> LAST_COOKED_ITEM = Tag.String(\"vri:furnace:last_cooked_item\")\n                    .map(Material::fromKey, mat -> mat.key().toString())\n                    .defaultValue(Material.AIR);\n\n            // The number of ticks that the furnace has been cooking for\n            Tag<Integer> COOKING_PROGRESS = Tag.Integer(\"vri:furnace:cooking_progress\").defaultValue(0);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/utils/DependencySorting.java",
    "content": "package net.minestom.vanilla.utils;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class DependencySorting {\n\n    public interface NamespaceDependent<T> {\n        T identity();\n        Set<T> dependencies();\n    }\n\n    public static <T, ND extends NamespaceDependent<T>> List<ND> sort(Set<ND> dependants) {\n        List<ND> sorted = new ArrayList<>();\n        Set<T> visited = new HashSet<>();\n        for (ND dependant : dependants) {\n            visit(dependant, dependants, sorted, visited);\n        }\n        return List.copyOf(sorted);\n    }\n\n    private static <T, ND extends NamespaceDependent<T>> void visit(ND dependant, Set<ND> dependants, List<ND> sorted, Set<T> visited) {\n        T identity = dependant.identity();\n        if (visited.contains(identity)) {\n            return;\n        }\n        visited.add(identity);\n        for (T dependency : dependant.dependencies()) {\n            ND dependencyDependant = dependants.stream()\n                    .filter(d -> d.identity().equals(dependency))\n                    .findFirst()\n                    .orElseThrow(() -> new IllegalStateException(\"Missing dependency \" + dependency + \" for \" + identity));\n            visit(dependencyDependant, dependants, sorted, visited);\n        }\n        sorted.add(dependant);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/utils/JavaUtils.java",
    "content": "package net.minestom.vanilla.utils;\n\nimport java.util.Collection;\nimport java.util.random.RandomGenerator;\n\npublic class JavaUtils {\n\n    public static <T> T randomElement(RandomGenerator random, Collection<T> collection) {\n        int size = collection.size();\n        int index = random.nextInt(size);\n        for (T element : collection) {\n            if (index == 0) {\n                return element;\n            }\n            index--;\n        }\n        throw new IllegalStateException(\"Collection size changed during iteration\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/utils/MathUtils.java",
    "content": "package net.minestom.vanilla.utils;\n\nimport net.minestom.server.coordinate.Point;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\npublic class MathUtils {\n    public static void forEachWithinManhattanDistance(Point origin, int distance, Consumer<Point> consumer) {\n        for (int i = 0; i <= distance; i++) {\n            for (int j = 0; i + j <= distance; j++) {\n                for (int k = 0; i + j + k <= distance; k++) {\n                    if (i == 0 && j == 0 && k == 0) {\n                        continue;\n                    }\n                    consumer.accept(origin.add(i, j, k));\n                    if (i != 0) {\n                        consumer.accept(origin.add(-i, j, k));\n                        if (j != 0) consumer.accept(origin.add(-i, -j, k));\n                        if (k != 0) {\n                            consumer.accept(origin.add(-i, j, -k));\n                            consumer.accept(origin.add(i, j, -k));\n                            consumer.accept(origin.add(-i, -j, -k));\n                        }\n                    }\n                    if (j != 0) {\n                        consumer.accept(origin.add(i, -j, k));\n                        if (k != 0) consumer.accept(origin.add(i, -j, -k));\n                    }\n                    if (k != 0) consumer.accept(origin.add(i, j, -k));\n                }\n            }\n        }\n    }\n\n    public static Set<Point> getWithinManhattanDistance(Point origin, int distance) {\n        Set<Point> points = new HashSet<>();\n        forEachWithinManhattanDistance(origin, distance, points::add);\n        return points;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/utils/MinestomUtils.java",
    "content": "package net.minestom.vanilla.utils;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.registry.RegistryKey;\n\npublic class MinestomUtils {\n\n    /**\n     * Initializes the resources of Minestom.\n     * This is used to not interfere with the timing of initialising the vanilla modules.\n     */\n    public static void initialize() {\n        Block.values();\n        Material.values();\n        EntityType.values();\n    }\n\n    public static int getEnchantLevel(ItemStack itemStack, Enchantment enchantment, int defaultValue) {\n        RegistryKey<Enchantment> enchant = getEnchantKey(enchantment);\n        if (enchant == null) return defaultValue;\n        EnchantmentList enchants = itemStack.get(DataComponents.ENCHANTMENTS);\n        if (enchants == null) return defaultValue;\n        if (!enchants.has(enchant)) return defaultValue;\n        return enchants.level(enchant);\n    }\n\n    public static RegistryKey<Enchantment> getEnchantKey(Enchantment enchantment) {\n        return MinecraftServer.getEnchantmentRegistry().getKey(enchantment);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/net/minestom/vanilla/utils/ZipUtils.java",
    "content": "package net.minestom.vanilla.utils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.*;\nimport java.util.regex.Pattern;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\n\npublic class ZipUtils {\n\n    private static final Comparator<String> COMPARATOR_STRING_DESC = (str1, str2) -> str1 == null ? 1 : str2 == null ? -1 : -str1.compareTo(str2);\n\n    public static Map<String, byte[]> unzip(InputStream is) throws IOException {\n        try (ZipInputStream in = new ZipInputStream(is)) {\n            ZipEntry entry;\n            Map<String, byte[]> content = new HashMap<>();\n            Set<String> dirs = new TreeSet<>(COMPARATOR_STRING_DESC);\n\n            while ((entry = in.getNextEntry()) != null) {\n                String path = removeDirectoryMarker(replaceIncorrectFileSeparators(entry.getName()));\n\n                if (isDirectory(entry)) {\n                    dirs.add(path);\n                } else {\n                    content.put(path, in.readAllBytes());\n                }\n            }\n\n            addOnlyEmptyDirectories(dirs, content);\n\n            return content.isEmpty() ? Collections.emptyMap() : content;\n        }\n    }\n\n    private static boolean isDirectory(ZipEntry entry) {\n        return entry.isDirectory() || entry.getName().endsWith(ILLEGAL_DIR_MARKER);\n    }\n\n    private static void addOnlyEmptyDirectories(Set<String> dirs, Map<String, byte[]> content) {\n        if (dirs.isEmpty()) {\n            return;\n        }\n\n        Set<String> paths = new HashSet<>(content.keySet());\n\n        for (String dir : dirs) {\n            boolean empty = true;\n\n            for (String path : paths) {\n                if (path.startsWith(dir)) {\n                    empty = false;\n                    break;\n                }\n            }\n\n            if (empty) {\n                content.put(dir, null);\n            }\n        }\n    }\n\n    private static final String DIR_MARKER = \"/\";\n    private static final String ILLEGAL_DIR_MARKER = \"\\\\\";\n    private static final Pattern BACK_SLASH = Pattern.compile(\"\\\\\\\\\");\n\n    private static String removeDirectoryMarker(String path) {\n        return path.endsWith(DIR_MARKER) || path.endsWith(ILLEGAL_DIR_MARKER) ? path.substring(0, path.length() - 1) : path;\n    }\n\n    private static String replaceIncorrectFileSeparators(String path) {\n        return BACK_SLASH.matcher(path).replaceAll(DIR_MARKER);\n    }\n}\n"
  },
  {
    "path": "core/src/main/resources/META-INF/services/org.tinylog.writers.Writer",
    "content": "net.minestom.vanilla.VanillaReimplementationWriter"
  },
  {
    "path": "core/src/test/java/net/minestom/vanilla/files/FileSystemTests.java",
    "content": "package net.minestom.vanilla.files;\n\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class FileSystemTests {\n    @Test\n    public void testDynamicWriteRead() {\n        DynamicFileSystem<String> fs = new DynamicFileSystem<>();\n\n        assertTrue(fs.folders().isEmpty());\n        assertTrue(fs.files().isEmpty());\n\n        fs.addFile(\"test.txt\", \"Hello, world!\");\n        fs.addFile(\"test2.txt\", \"Hello, world!\");\n        fs.addFile(\"test3.txt\", \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\");\n\n        assertEquals(3, fs.files().size());\n        assertEquals(\"Hello, world!\", fs.file(\"test.txt\"));\n        assertEquals(\"Hello, world!\", fs.file(\"test2.txt\"));\n        assertEquals(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\", fs.file(\"test3.txt\"));\n\n        fs.folder(\"testDir\");\n\n        assertEquals(0, fs.folders().size());\n\n        fs.addFolder(\"testDir\").addFile(\"test4.txt\", \"Hello, world!\");\n\n        assertEquals(1, fs.folders().size());\n        assertEquals(3, fs.files().size());\n        assertEquals(1, fs.folder(\"testDir\").files().size());\n        assertEquals(\"Hello, world!\", fs.folder(\"testDir\").file(\"test4.txt\"));\n    }\n}\n"
  },
  {
    "path": "crafting/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":datapack\"))\n    implementation(\"net.goldenstack:window:${project.property(\"window_version\")}\")\n}"
  },
  {
    "path": "crafting/src/main/java/net/minestom/vanilla/crafting/CraftingFeature.java",
    "content": "package net.minestom.vanilla.crafting;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.player.PlayerBlockInteractEvent;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.vanilla.datapack.Datapacks;\nimport net.minestom.vanilla.logging.Logger;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.*;\n\npublic class CraftingFeature {\n\n    public record Recipes(@NotNull Crafting crafting,\n                          @NotNull Smelting smelting,\n                          @NotNull Smithing smithing,\n                          @NotNull Map<Key, Recipe.Stonecutting> stonecutting,\n                          @NotNull Map<Key, Recipe> special) {\n        public Recipes {\n            stonecutting = Map.copyOf(stonecutting);\n            special = Map.copyOf(special);\n        }\n\n        public static @NotNull Recipes fromRaw(@NotNull Map<Key, Recipe> recipes) {\n            Set<Map.Entry<Key, Recipe>> entries = recipes.entrySet();\n\n            Recipes parsedRecipes = new Recipes(\n                    new Recipes.Crafting(\n                            filterRecipes(entries, Recipe.Crafting.Shaped.class),\n                            filterRecipes(entries, Recipe.Crafting.Shapeless.class),\n                            filterRecipes(entries, Recipe.Crafting.Transmute.class)\n                    ),\n                    new Recipes.Smelting(\n                            filterRecipes(entries, Recipe.Cooking.Smelting.class),\n                            filterRecipes(entries, Recipe.Cooking.Smoking.class),\n                            filterRecipes(entries, Recipe.Cooking.Blasting.class),\n                            filterRecipes(entries, Recipe.Cooking.Campfire.class)\n                    ),\n                    new Recipes.Smithing(\n                            filterRecipes(entries, Recipe.Smithing.Transform.class),\n                            filterRecipes(entries, Recipe.Smithing.Trim.class)\n                    ),\n                    filterRecipes(entries, Recipe.Stonecutting.class),\n                    filterRecipes(entries, Recipe.class) // All remaining\n            );\n\n            if (!entries.isEmpty()) throw new RuntimeException(\"Entries should be empty!\");\n\n            return parsedRecipes;\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        private static <T extends Recipe> @NotNull Map<Key, T> filterRecipes(@NotNull Iterable<Map.Entry<Key, Recipe>> entries, @NotNull Class<T> clazz) {\n            Map<Key, T> map = new HashMap<>();\n\n            var iter = entries.iterator();\n            while (iter.hasNext()) {\n                var entry = iter.next();\n\n                if (clazz.isInstance(entry.getValue())) {\n                    map.put(entry.getKey(), (T) entry.getValue());\n                    iter.remove();\n                }\n            }\n\n            return map;\n        }\n\n        public record Crafting(@NotNull Map<Key, Recipe.Crafting.Shaped> shaped,\n                               @NotNull Map<Key, Recipe.Crafting.Shapeless> shapeless,\n                               @NotNull Map<Key, Recipe.Crafting.Transmute> transmute) {\n            public Crafting {\n                shaped = Map.copyOf(shaped);\n                shapeless = Map.copyOf(shapeless);\n                transmute = Map.copyOf(transmute);\n            }\n        }\n\n        public record Smelting(@NotNull Map<Key, Recipe.Cooking.Smelting> smelting,\n                               @NotNull Map<Key, Recipe.Cooking.Smoking> smoking,\n                               @NotNull Map<Key, Recipe.Cooking.Blasting> blasting,\n                               @NotNull Map<Key, Recipe.Cooking.Campfire> campfire) {\n            public Smelting {\n                smelting = Map.copyOf(smelting);\n                smoking = Map.copyOf(smoking);\n                blasting = Map.copyOf(blasting);\n                campfire = Map.copyOf(campfire);\n            }\n        }\n\n        public record Smithing(@NotNull Map<Key, Recipe.Smithing.Transform> transform,\n                               @NotNull Map<Key, Recipe.Smithing.Trim> trim) {\n            public Smithing {\n                transform = Map.copyOf(transform);\n                trim = Map.copyOf(trim);\n            }\n        }\n    }\n\n    public static @NotNull Map<Key, Recipe> buildFromDatapack(@NotNull ServerProcess process) {\n        final Path recipesPath = Path.of(\"/\", \"data\", \"minecraft\", \"recipe\");\n\n        Map<Key, Recipe> recipes;\n\n        try {\n            Path jar = Datapacks.ensureCurrentJarExists();\n\n            recipes = Datapacks.buildRegistryFromJar(jar, recipesPath, process, \".json\", Recipe.CODEC);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        Logger.info(\"Loaded and parsed \" + recipes.size() + \" recipes\");\n        return recipes;\n    }\n\n    public static @NotNull EventNode<Event> createEventNode(@NotNull Map<Key, Recipe> recipeMap, @NotNull ServerProcess process) {\n        // Register recipes\n        recipeMap.values().forEach(process.recipe()::addRecipe);\n\n        // Parse recipes into usable form\n        Recipes recipes = Recipes.fromRaw(recipeMap);\n\n        return EventNode.all(\"vri:recipes\")\n                .addChild(new CraftingRecipes(recipes, process).init())\n                .addListener(PlayerBlockInteractEvent.class, event -> {\n                    if (event.getBlock().compare(Block.CRAFTING_TABLE)) event.getPlayer().openInventory(new Inventory(InventoryType.CRAFTING, \"Crafting\"));\n                });\n    }\n\n}\n"
  },
  {
    "path": "crafting/src/main/java/net/minestom/vanilla/crafting/CraftingRecipes.java",
    "content": "package net.minestom.vanilla.crafting;\n\nimport net.goldenstack.window.InventoryView;\nimport net.goldenstack.window.Views;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.inventory.InventoryCloseEvent;\nimport net.minestom.server.event.inventory.InventoryItemChangeEvent;\nimport net.minestom.server.event.inventory.InventoryPreClickEvent;\nimport net.minestom.server.inventory.AbstractInventory;\nimport net.minestom.server.inventory.Inventory;\nimport net.minestom.server.inventory.InventoryType;\nimport net.minestom.server.inventory.PlayerInventory;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.registry.RegistryTag;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\npublic record CraftingRecipes(@NotNull CraftingFeature.Recipes recipes, @NotNull ServerProcess process) {\n\n    public EventNode<Event> init() {\n        EventNode<Event> node = EventNode.all(\"vri:recipes-inventory\");\n\n        node.addListener(InventoryItemChangeEvent.class, addSquareInputSlots(\n                inv -> inv instanceof PlayerInventory,\n                Views.player().crafting().input(),\n                Views.player().crafting().output()\n        )).addListener(InventoryItemChangeEvent.class, addSquareInputSlots(\n                inv -> inv instanceof Inventory crafting && crafting.getInventoryType() == InventoryType.CRAFTING,\n                Views.craftingTable().input(),\n                Views.craftingTable().output()\n        )).addListener(InventoryPreClickEvent.class, addOutputSlot(\n                inv -> inv instanceof PlayerInventory,\n                Views.player().crafting().input(),\n                Views.player().crafting().output()\n        )).addListener(InventoryPreClickEvent.class, addOutputSlot(\n                inv -> inv instanceof Inventory crafting && crafting.getInventoryType() == InventoryType.CRAFTING,\n                Views.craftingTable().input(),\n                Views.craftingTable().output()\n        ));\n\n        return node;\n    }\n\n    // Assumes that the input region is square.\n    private @NotNull Consumer<InventoryItemChangeEvent> addSquareInputSlots(@NotNull Predicate<AbstractInventory> predicate, @NotNull InventoryView input, @NotNull InventoryView.Singular output) {\n        return event -> {\n            final AbstractInventory inv = event.getInventory();\n\n            if (!predicate.test(inv)) return;\n            if (!input.isValidExternal(event.getSlot()) && !output.isValidExternal(event.getSlot())) return;\n\n            int length = (int) Math.round(Math.sqrt(input.size()));\n\n            Recipe.Crafting recipe = searchRecipe(length, length, input.collect(inv));\n\n            output.set(inv, recipe != null ? recipe.result() : ItemStack.AIR);\n        };\n    }\n\n    private @NotNull Consumer<InventoryPreClickEvent> addOutputSlot(@NotNull Predicate<AbstractInventory> predicate, @NotNull InventoryView input, @NotNull InventoryView.Singular output) {\n        return event -> {\n            final AbstractInventory inv = event.getInventory();\n\n            if (!predicate.test(inv)) return;\n            if (!output.isValidExternal(event.getSlot())) return;\n\n            final ItemStack clicked = output.get(inv);\n\n            if (clicked.isAir()) {\n                event.setCancelled(true);\n                return;\n            }\n\n            final PlayerInventory playerInv = event.getPlayer().getInventory();\n            final ItemStack cursor = playerInv.getCursorItem();\n\n            event.setCancelled(true);\n\n            if (clicked.isSimilar(cursor)) {\n                if (clicked.amount() + cursor.amount() > cursor.maxStackSize()) {\n                    return;\n                }\n\n                playerInv.setCursorItem(cursor.withAmount(cursor.amount() + clicked.amount()));\n            } else if (cursor.isAir()) {\n                playerInv.setCursorItem(clicked);\n            } else {\n                return;\n            }\n\n            output.set(inv, ItemStack.AIR);\n\n            for (int i = 0; i < input.size(); i++) {\n                input.set(inv, i, input.get(inv, i).consume(1));\n            }\n        };\n    }\n\n    public @Nullable Recipe.Crafting searchRecipe(int width, int height, @NotNull List<ItemStack> ingredients) {\n        final CraftingFeature.Recipes.Crafting crafting = recipes.crafting();\n\n        List<ItemStack> nonAir = new ArrayList<>();\n        for (ItemStack item : ingredients) if (!item.isAir()) nonAir.add(item);\n\n        // Try shapeless\n        for (Recipe.Crafting.Shapeless shapeless : crafting.shapeless().values()) {\n            if (tryShapeless(shapeless, nonAir)) return shapeless;\n        }\n\n        // Try shaped\n        for (Recipe.Crafting.Shaped shaped : crafting.shaped().values()) {\n            if (tryShaped(shaped, width, height, ingredients)) return shaped;\n        }\n\n        // Try transmute\n\n        if (nonAir.size() == 2) {\n            for (Recipe.Crafting.Transmute transmute : crafting.transmute().values()) {\n                if (tryTransmute(transmute, nonAir.getFirst(), nonAir.get(1))) return transmute;\n            }\n        }\n\n        // TODO: Implement special recipes\n//        for (Recipe recipe : recipes.special().values()) {\n//\n//        }\n\n        return null;\n    }\n\n    public boolean tryShapeless(@NotNull Recipe.Crafting.Shapeless recipe, @NotNull List<ItemStack> ingredients) {\n        if (recipe.ingredients().size() != ingredients.size()) return false;\n\n        // Taking the first valid one strategically has potentially invalid behaviour if we, for example, take an entire\n        // tag instead of a single item, potentially invalidating an otherwise valid recipe had an individual material\n        // been chosen.\n        // We amend this by taking the smallest item first, in terms of the number of options. This is technically not\n        // correct, but should be in enough cases. The correct solution for this is probably some variant of the\n        // knapsack problem, but that might be too much effort for this.\n        // This algorithm should have time complexity O(n^2), but n is always microscopic (n <= 9) so it doesn't really\n        // matter here.\n\n        boolean[] used = new boolean[ingredients.size()];\n\n        // For each material...\n        for (ItemStack item : ingredients) {\n            int indexOfSmallest = -1;\n\n            // Find the smallest unused tag that fulfills this material\n            for (int index = 0; index < ingredients.size(); index++) {\n                RegistryTag<Material> current = recipe.ingredients().get(index);\n\n                // Only pick ones that fulfill this one and aren't already used\n                if (used[index] || !current.contains(item.material())) continue;\n\n                if (indexOfSmallest == -1 || current.size() < recipe.ingredients().get(indexOfSmallest).size()) {\n                    indexOfSmallest = index;\n                }\n\n                // Quick exit if smallest\n                if (current.size() == 1) break;\n            }\n\n            // If there's nothing for this material, invalid.\n            if (indexOfSmallest == -1) return false;\n\n            // Mark it as true so it isn't reused.\n            used[indexOfSmallest] = true;\n        }\n\n        return true;\n    }\n\n    /**\n     * Tries a shaped recipe. Assumes that {@code materials} has a length of {@code width * height}.\n     */\n    public boolean tryShaped(@NotNull Recipe.Crafting.Shaped shaped, int width, int height, @NotNull List<ItemStack> ingredients) {\n        // First, find the size of the recipe inside the grid.\n        int minCol = Integer.MAX_VALUE;\n        int minRow = Integer.MAX_VALUE;\n        int maxCol = Integer.MIN_VALUE;\n        int maxRow = Integer.MIN_VALUE;\n\n        for (int row = 0; row < height; row++) {\n            for (int col = 0; col < width; col++) {\n                if (ingredients.get(row * height + col).isAir()) continue;\n\n                if (row < minRow) minRow = row;\n                if (col < minCol) minCol = col;\n                if (row > maxRow) maxRow = row;\n                if (col > maxCol) maxCol = col;\n            }\n        }\n\n        // There are no items in the grid if anything is still default, but if there's any value at all, it will set all\n        // four values, so we only need to check one.\n        if (minCol == Integer.MAX_VALUE) return false;\n\n        final int shapeWidth = maxCol - minCol + 1;\n        final int shapeHeight = maxRow - minRow + 1;\n\n        if (shapeHeight != shaped.pattern().size()) return false;\n        if (shapeWidth != shaped.pattern().getFirst().length()) return false;\n\n        return tryShapedInPosition(shaped, ingredients, width, height, minRow, minCol);\n    }\n\n    // Assumes the shaped recipe can fit in the material grid and that nothing has 0 size.\n    private boolean tryShapedInPosition(@NotNull Recipe.Crafting.Shaped shaped, @NotNull List<ItemStack> ingredients, int width, int height, int startRow, int startCol) {\n        for (int row = 0; row < shaped.pattern().size(); row++) {\n            final String rowPattern = shaped.pattern().get(row);\n\n            for (int col = 0; col < rowPattern.length(); col++) {\n                final char charKey = rowPattern.charAt(col);\n                final Material existing = ingredients.get((row + startRow) * height + (col + startCol)).material();\n\n                final String key = String.valueOf(charKey);\n                if (!shaped.key().getOrDefault(key, RegistryTag.direct(Material.AIR)).contains(existing)) {\n                    return false;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Tries a transmute recipe.\n     */\n    public boolean tryTransmute(@NotNull Recipe.Crafting.Transmute transmute, @NotNull ItemStack first, @NotNull ItemStack second) {\n        return (transmute.input().contains(first.material()) && transmute.material().contains(second.material()))\n                || (transmute.material().contains(first.material()) && transmute.input().contains(second.material()));\n    }\n}\n\n"
  },
  {
    "path": "crafting/src/main/java/net/minestom/vanilla/crafting/Recipe.java",
    "content": "package net.minestom.vanilla.crafting;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.recipe.RecipeBookCategory;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.registry.Registries;\nimport net.minestom.server.registry.RegistryTag;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * All recipe types that exist.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic sealed interface Recipe extends net.minestom.server.recipe.Recipe {\n\n    @NotNull StructCodec<Recipe> CODEC = Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends Recipe>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, Recipe::codec, \"type\");\n\n    static @NotNull DynamicRegistry<StructCodec<? extends Recipe>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends Recipe>> registry = DynamicRegistry.create(Key.key(\"recipes\"));\n\n        // Crafting table recipes\n        registry.register(\"crafting_shaped\", Crafting.Shaped.CODEC);\n        registry.register(\"crafting_shapeless\", Crafting.Shapeless.CODEC);\n        registry.register(\"crafting_transmute\", Crafting.Transmute.CODEC);\n\n        // Smelting\n        registry.register(\"smelting\", Cooking.Smelting.CODEC);\n        registry.register(\"smoking\", Cooking.Smoking.CODEC);\n        registry.register(\"blasting\", Cooking.Blasting.CODEC);\n        registry.register(\"campfire_cooking\", Cooking.Campfire.CODEC);\n\n        // Smithing & stonecutting\n        registry.register(\"smithing_transform\", Smithing.Transform.CODEC);\n        registry.register(\"smithing_trim\", Smithing.Trim.CODEC);\n        registry.register(\"stonecutting\", Stonecutting.CODEC);\n\n        // Special recipes\n        registry.register(\"crafting_decorated_pot\", DecoratedPot.CODEC);\n        registry.register(\"crafting_special_armordye\", SpecialArmorDye.CODEC);\n        registry.register(\"crafting_special_bannerduplicate\", SpecialBannerDuplicate.CODEC);\n        registry.register(\"crafting_special_bookcloning\", SpecialBookCloning.CODEC);\n        registry.register(\"crafting_special_firework_rocket\", SpecialFireworkRocket.CODEC);\n        registry.register(\"crafting_special_firework_star\", SpecialFireworkStar.CODEC);\n        registry.register(\"crafting_special_firework_star_fade\", SpecialFireworkStarFade.CODEC);\n        registry.register(\"crafting_special_mapcloning\", SpecialMapCloning.CODEC);\n        registry.register(\"crafting_special_mapextending\", SpecialMapExtending.CODEC);\n        registry.register(\"crafting_special_repairitem\", SpecialRepairItem.CODEC);\n        registry.register(\"crafting_special_shielddecoration\", SpecialShieldDecoration.CODEC);\n        registry.register(\"crafting_special_tippedarrow\", SpecialTippedArrow.CODEC);\n        return registry;\n    }\n\n    /**\n     * A crafting recipe - either shaped, shapeless, transmute, or the decorated pot recipe.\n     */\n    sealed interface Crafting extends Recipe {\n\n        /**\n         * @return the recipe book category of this recipe\n         */\n        @NotNull Category category();\n\n        /**\n         * @return the item that this recipe produces\n         */\n        @NotNull ItemStack result();\n\n        @Override\n        default @Nullable RecipeBookCategory recipeBookCategory() {\n            return category().category;\n        }\n\n        enum Category {\n            EQUIPMENT(RecipeBookCategory.CRAFTING_EQUIPMENT),\n            BUILDING(RecipeBookCategory.CRAFTING_BUILDING_BLOCKS),\n            MISC(RecipeBookCategory.CRAFTING_MISC),\n            REDSTONE(RecipeBookCategory.CRAFTING_REDSTONE);\n\n            private final RecipeBookCategory category;\n            Category(RecipeBookCategory category) {\n                this.category = category;\n            }\n\n            public static final @NotNull Codec<Category> CODEC = Codec.Enum(Category.class);\n        }\n\n        record Shaped(@Nullable String recipeBookGroup, @NotNull Category category, @NotNull ItemStack result, boolean showNotification, @NotNull List<String> pattern, @NotNull Map<String, RegistryTag<Material>> key) implements Crafting {\n            public static final @NotNull StructCodec<Shaped> CODEC = StructCodec.struct(\n                    \"group\", Codec.STRING.optional(), Shaped::recipeBookGroup,\n                    \"category\", Category.CODEC.optional(Category.MISC), Shaped::category,\n                    \"result\", ItemStack.CODEC, Shaped::result,\n                    \"show_notification\", Codec.BOOLEAN.optional(true), Shaped::showNotification,\n                    \"pattern\", Codec.STRING.list(), Shaped::pattern,\n                    \"key\", Codec.STRING.mapValue(RegistryTag.codec(Registries::material)), Shaped::key,\n                    Shaped::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        record Shapeless(@Nullable String recipeBookGroup, @NotNull Category category, @NotNull ItemStack result, @NotNull List<RegistryTag<Material>> ingredients) implements Crafting {\n            public static final @NotNull StructCodec<Shapeless> CODEC = StructCodec.struct(\n                    \"group\", Codec.STRING.optional(), Shapeless::recipeBookGroup,\n                    \"category\", Category.CODEC.optional(Category.MISC), Shapeless::category,\n                    \"result\", ItemStack.CODEC, Shapeless::result,\n                    \"ingredients\", RegistryTag.codec(Registries::material).list(), Shapeless::ingredients,\n                    Shapeless::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        record Transmute(@Nullable String recipeBookGroup, @NotNull Category category, @NotNull ItemStack result, @NotNull RegistryTag<Material> input, @NotNull RegistryTag<Material> material) implements Crafting {\n            public static final @NotNull StructCodec<Transmute> CODEC = StructCodec.struct(\n                    \"group\", Codec.STRING.optional(), Transmute::recipeBookGroup,\n                    \"category\", Category.CODEC.optional(Category.MISC), Transmute::category,\n                    \"result\", ItemStack.CODEC, Transmute::result,\n                    \"input\", RegistryTag.codec(Registries::material), Transmute::input,\n                    \"material\", RegistryTag.codec(Registries::material), Transmute::material,\n                    Transmute::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n    }\n\n    record Cooking(@Nullable String recipeBookGroup, @Nullable Category category, @NotNull RegistryTag<Material> ingredient, @NotNull ItemStack result, int cookingTime, double experience) {\n        public enum Category {\n            FOOD, BLOCKS, MISC;\n\n            public static final @NotNull Codec<Category> CODEC = Codec.Enum(Category.class);\n        }\n\n        public static @NotNull Codec<Cooking> codec(@Nullable Category defaultCategory) {\n            return StructCodec.struct(\n                    \"group\", Codec.STRING.optional(), Cooking::recipeBookGroup,\n                    \"category\", defaultCategory == null ? Category.CODEC.optional() : Category.CODEC.optional(defaultCategory), Cooking::category,\n                    \"ingredient\", RegistryTag.codec(Registries::material), Cooking::ingredient,\n                    \"result\", ItemStack.CODEC, Cooking::result,\n                    \"cookingtime\", Codec.INT.optional(100), Cooking::cookingTime,\n                    \"experience\", Codec.DOUBLE.optional(0D), Cooking::experience,\n                    Cooking::new\n            );\n        }\n\n        public record Smelting(@NotNull Cooking cooking) implements Recipe {\n            public static final @NotNull StructCodec<Smelting> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Cooking.codec(Category.MISC), Smelting::cooking,\n                    Smelting::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        public record Smoking(@NotNull Cooking cooking) implements Recipe {\n            public static final @NotNull StructCodec<Smoking> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Cooking.codec(Category.FOOD), Smoking::cooking,\n                    Smoking::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        public record Blasting(@NotNull Cooking cooking) implements Recipe {\n            public static final @NotNull StructCodec<Blasting> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Cooking.codec(Category.MISC), Blasting::cooking,\n                    Blasting::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        public record Campfire(@NotNull Cooking cooking) implements Recipe {\n            public static final @NotNull StructCodec<Campfire> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Cooking.codec(null), Campfire::cooking,\n                    Campfire::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n    }\n\n    record Smithing(@NotNull RegistryTag<Material> template, @NotNull RegistryTag<Material> base, @NotNull RegistryTag<Material> addition) {\n\n        public static final @NotNull Codec<Smithing> CODEC = StructCodec.struct(\n                \"template\", RegistryTag.codec(Registries::material), Smithing::template,\n                \"base\", RegistryTag.codec(Registries::material), Smithing::base,\n                \"addition\", RegistryTag.codec(Registries::material), Smithing::addition,\n                Smithing::new\n        );\n\n        public record Transform(@NotNull Smithing smithing, @NotNull ItemStack result) implements Recipe {\n            public static final @NotNull StructCodec<Transform> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Smithing.CODEC, Transform::smithing,\n                    \"result\", ItemStack.CODEC, Transform::result,\n                    Transform::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n        public record Trim(@NotNull Smithing smithing) implements Recipe {\n            public static final @NotNull StructCodec<Trim> CODEC = StructCodec.struct(\n                    StructCodec.INLINE, Smithing.CODEC, Trim::smithing,\n                    Trim::new\n            );\n\n            @Override\n            public @NotNull StructCodec<? extends Recipe> codec() {\n                return CODEC;\n            }\n        }\n\n    }\n\n    record Stonecutting(@NotNull RegistryTag<Material> ingredient, @NotNull ItemStack result) implements Recipe {\n        public static final @NotNull StructCodec<Stonecutting> CODEC = StructCodec.struct(\n                \"ingredient\", RegistryTag.codec(Registries::material), Stonecutting::ingredient,\n                \"result\", ItemStack.CODEC, Stonecutting::result,\n                Stonecutting::new\n        );\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record DecoratedPot(@NotNull Recipe.Crafting.Category category) implements Recipe {\n        public static final @NotNull StructCodec<DecoratedPot> CODEC = StructCodec.struct(\n                \"category\", Crafting.Category.CODEC.optional(Crafting.Category.MISC), DecoratedPot::category,\n                DecoratedPot::new\n        );\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialArmorDye() implements Recipe {\n        public static final @NotNull StructCodec<SpecialArmorDye> CODEC = StructCodec.struct(SpecialArmorDye::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialBannerDuplicate() implements Recipe {\n        public static final @NotNull StructCodec<SpecialBannerDuplicate> CODEC = StructCodec.struct(SpecialBannerDuplicate::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialBookCloning() implements Recipe {\n        public static final @NotNull StructCodec<SpecialBookCloning> CODEC = StructCodec.struct(SpecialBookCloning::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialFireworkRocket() implements Recipe {\n        public static final @NotNull StructCodec<SpecialFireworkRocket> CODEC = StructCodec.struct(SpecialFireworkRocket::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialFireworkStar() implements Recipe {\n        public static final @NotNull StructCodec<SpecialFireworkStar> CODEC = StructCodec.struct(SpecialFireworkStar::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialFireworkStarFade() implements Recipe {\n        public static final @NotNull StructCodec<SpecialFireworkStarFade> CODEC = StructCodec.struct(SpecialFireworkStarFade::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialMapCloning() implements Recipe {\n        public static final @NotNull StructCodec<SpecialMapCloning> CODEC = StructCodec.struct(SpecialMapCloning::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialMapExtending() implements Recipe {\n        public static final @NotNull StructCodec<SpecialMapExtending> CODEC = StructCodec.struct(SpecialMapExtending::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialRepairItem() implements Recipe {\n        public static final @NotNull StructCodec<SpecialRepairItem> CODEC = StructCodec.struct(SpecialRepairItem::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialShieldDecoration() implements Recipe {\n        public static final @NotNull StructCodec<SpecialShieldDecoration> CODEC = StructCodec.struct(SpecialShieldDecoration::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    record SpecialTippedArrow() implements Recipe {\n        public static final @NotNull StructCodec<SpecialTippedArrow> CODEC = StructCodec.struct(SpecialTippedArrow::new);\n\n        @Override\n        public @NotNull StructCodec<? extends Recipe> codec() {\n            return CODEC;\n        }\n    }\n\n    /**\n     * @return the codec that can encode this recipe\n     */\n    @NotNull StructCodec<? extends Recipe> codec();\n\n}\n"
  },
  {
    "path": "datapack/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "datapack/src/main/java/net/minestom/vanilla/datapack/Datapacks.java",
    "content": "package net.minestom.vanilla.datapack;\n\nimport com.google.gson.JsonParser;\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.BinaryTag;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.adventure.MinestomAdventure;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.Result;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.codec.Transcoder;\nimport net.minestom.server.registry.RegistryTranscoder;\nimport net.minestom.vanilla.logging.Loading;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.file.FileSystem;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\n/**\n * Utilities for datapack loading. Primary functions are {@link Datapacks#ensureCurrentJarExists()} and {@link\n * Datapacks#buildRegistryFromJar(Path, Path, ServerProcess, String, Codec)}\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic class Datapacks {\n\n    public static final @NotNull URI VERSION_MANIFEST_URI = URI.create(\"https://launchermeta.mojang.com/mc/game/version_manifest.json\");\n\n    public static final @NotNull Path MOJANG_DATA_DIRECTORY = Path.of(\".\", \"mojang-data\");\n\n    /**\n     * Version manfest data, from {@link Datapacks#VERSION_MANIFEST_URI}.\n     */\n    public record VersionManifest(@NotNull Latest latest, @NotNull List<Version> versions) {\n        public static final @NotNull StructCodec<VersionManifest> CODEC = StructCodec.struct(\n                \"latest\", Latest.CODEC, VersionManifest::latest,\n                \"versions\", Version.CODEC.list(), VersionManifest::versions,\n                VersionManifest::new\n        );\n\n        public record Latest(@NotNull String release, @NotNull String snapshot) {\n            public static final @NotNull StructCodec<Latest> CODEC = StructCodec.struct(\n                    \"release\", Codec.STRING, Latest::release,\n                    \"snapshot\", Codec.STRING, Latest::snapshot,\n                    Latest::new\n            );\n        }\n\n        public record Version(@NotNull String id, @NotNull Type type, @NotNull String url, @NotNull String time, @NotNull String releaseTime) {\n            public static final @NotNull StructCodec<Version> CODEC = StructCodec.struct(\n                \"id\", Codec.STRING, Version::id,\n                    \"type\", Type.CODEC, Version::type,\n                    \"url\", Codec.STRING, Version::url,\n                    \"time\", Codec.STRING, Version::url,\n                    \"releaseTime\", Codec.STRING, Version::url,\n                    Version::new\n            );\n\n            public enum Type {\n                RELEASE, SNAPSHOT, OLD_BETA, OLD_ALPHA;\n\n                public static final @NotNull Codec<Type> CODEC = Codec.Enum(Type.class);\n            }\n        }\n    }\n\n    /**\n     * Version metadata. There's more information on the codec than this, but most of it is not useful here, so it is\n     * not included.\n     */\n    public record VersionMetadata(@NotNull Downloads downloads) {\n        public static final @NotNull StructCodec<VersionMetadata> CODEC = StructCodec.struct(\n                \"downloads\", Downloads.CODEC, VersionMetadata::downloads,\n                VersionMetadata::new\n        );\n\n        public record Downloads(@NotNull Download client, @Nullable Download clientMappings,\n                                @NotNull Download server, @Nullable Download serverMappings) {\n            public static final @NotNull StructCodec<Downloads> CODEC = StructCodec.struct(\n                    \"client\", Download.CODEC, Downloads::client,\n                    \"client_mappings\", Download.CODEC.optional(), Downloads::clientMappings,\n                    \"server\", Download.CODEC, Downloads::server,\n                    \"server_mappings\", Download.CODEC.optional(), Downloads::serverMappings,\n                    Downloads::new\n            );\n        }\n\n        public record Download(@NotNull String sha1, int size, @NotNull String url) {\n            public static final @NotNull StructCodec<Download> CODEC = StructCodec.struct(\n                    \"sha1\", Codec.STRING, Download::sha1,\n                    \"size\", Codec.INT, Download::size,\n                    \"url\", Codec.STRING, Download::url,\n                    Download::new\n            );\n        }\n\n    }\n\n    /**\n     * Extracts various metadata (including JAR URLs) about the specified version. Use \"latest\" for the latest release.\n     */\n    public static @NotNull URL getVersionMetadataURL(@NotNull String version) throws IOException {\n        URL discoveryUrl = VERSION_MANIFEST_URI.toURL();\n\n        VersionManifest manifest;\n\n        try (InputStream source = discoveryUrl.openStream();\n             InputStreamReader reader = new InputStreamReader(source)) {\n            Result<VersionManifest> result = VersionManifest.CODEC.decode(Transcoder.JSON, JsonParser.parseReader(reader));\n            manifest = result.orElseThrow(\"failed to parse version manifest at url \" + VERSION_MANIFEST_URI);\n        }\n\n        if (version.equals(\"latest\")) {\n            version = manifest.latest().release();\n        }\n\n        for (VersionManifest.Version versionEntry : manifest.versions()) {\n            if (versionEntry.id().equals(version)) {\n                return URI.create(versionEntry.url()).toURL();\n            }\n        }\n\n        throw new IllegalArgumentException(\"Version '\" + version + \"' not found!\");\n    }\n\n    /**\n     * Extracts the client JAR url from the version metadata.\n     */\n    public static @NotNull URL getClientJarURL(@NotNull URL versionMetadata) throws IOException {\n        VersionMetadata metadata;\n\n        try (InputStream source = versionMetadata.openStream();\n             InputStreamReader reader = new InputStreamReader(source)) {\n            Result<VersionMetadata> result = VersionMetadata.CODEC.decode(Transcoder.JSON, JsonParser.parseReader(reader));\n            metadata = result.orElseThrow(\"failed to parse version metadata at url \" + versionMetadata);\n        }\n\n        String url = metadata.downloads().client().url();\n        return URI.create(url).toURL();\n    }\n\n    /**\n     * Downloads the vanilla JAR from a specified source URL to the given sink file. Other than log messages, this is\n     * entirely agnostic of the actual file content and simply performs a copy while logging.\n     */\n    public static void downloadJar(@NotNull URL sourceUrl, @NotNull Path sinkFile) throws IOException {\n        URLConnection sourceConnection = sourceUrl.openConnection();\n        sourceConnection.connect();\n\n        final int parts = 8;\n\n        int len = sourceConnection.getContentLength();\n\n        double totalMB = (double) len / 1024 / 1024;\n        Loading.start(String.format(\"Downloading vanilla jar (%.2f MB)...\", totalMB));\n\n        byte[] buf = new byte[4096];\n\n        // Ensure necessary parent directories for the JAR exist\n        Files.createDirectories(sinkFile.getParent());\n\n        try (InputStream source = sourceConnection.getInputStream();\n             OutputStream sink = Files.newOutputStream(sinkFile)) {\n\n            for (int cur = 0, read; (read = source.read(buf)) > 0; cur += read) {\n                sink.write(buf, 0, read);\n\n                // If it passed a boundary (wraps around mod len/parts after reading), display progress\n                if ((cur + read) % (len/parts) < cur % (len/parts)) {\n                    double progress = cur / (double) len;\n                    Loading.updater().progress(Math.round(progress * parts) / (double) parts);\n                }\n            }\n        }\n\n        Loading.finish();\n    }\n\n    /**\n     * Downloads the client JAR for the specified version and places it in {@code client-VERSION.jar} within the\n     * specified directory. No-op of the client JAR has been downloaded already.\n     * @return the client JAR path\n     */\n    public static @NotNull Path discoverAndDownloadJar(@NotNull String version, @NotNull Path sinkDirectory) throws IOException {\n        Path sinkFile = sinkDirectory.resolve(\"client-\" + version + \".jar\");\n        if (Files.exists(sinkFile)) return sinkFile; // Cached\n\n        URL jarUrl = Datapacks.getClientJarURL(Datapacks.getVersionMetadataURL(version));\n\n        Datapacks.downloadJar(jarUrl, sinkFile);\n        return sinkFile;\n    }\n\n    /**\n     * Ensures that the client JAR exists and is downloaded, returning its path.\n     */\n    public static @NotNull Path ensureCurrentJarExists() throws IOException {\n        return Datapacks.discoverAndDownloadJar(MinecraftServer.VERSION_NAME, MOJANG_DATA_DIRECTORY);\n    }\n\n    @SuppressWarnings(\"PatternValidation\")\n    public static <T> @NotNull Map<Key, T> buildRegistryFromJar(@NotNull Path jarPath, @NotNull Path pathFilter, @NotNull ServerProcess process, @NotNull String fileSuffix, @NotNull Codec<T> codec) throws IOException {\n        final Map<Key, T> map = new HashMap<>();\n        final Transcoder<BinaryTag> coder = new RegistryTranscoder<>(Transcoder.NBT, process);\n\n        try (FileSystem fileSystem = FileSystems.newFileSystem(jarPath)) {\n            for (Path root : fileSystem.getRootDirectories()) {\n                Path relevantFiles = root.resolve(pathFilter.toString()); // Prevent provider mismatch\n\n                try (Stream<Path> paths = Files.walk(relevantFiles)) {\n                    for (Path path : paths.toList()) {\n                        if (!Files.isRegularFile(path)) continue;\n                        if (!path.toString().endsWith(\".json\")) continue;\n\n                        String keyPath = relevantFiles.relativize(path).toString();\n                        keyPath = keyPath.substring(0, keyPath.length() - fileSuffix.length());\n\n                        BinaryTag tag;\n                        try {\n                            tag = MinestomAdventure.tagStringIO().asTag(Files.readString(path));\n                        } catch (IOException e) {\n                            throw new RuntimeException(e);\n                        }\n\n                        map.put(\n                                Key.key(keyPath),\n                                codec.decode(coder, tag).orElseThrow(\"parsing \" + path)\n                        );\n                    }\n                }\n            }\n        }\n\n        return map;\n    }\n\n}"
  },
  {
    "path": "datapack-loading/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":mojang-data\"))\n    implementation(\"space.vectrix.flare:flare:2.0.1\")\n    implementation(\"space.vectrix.flare:flare-fastutil:2.0.1\")\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/Datapack.java",
    "content": "package net.minestom.vanilla.datapack;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.advancement.Advancement;\nimport net.minestom.vanilla.datapack.dimension.DimensionType;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\nimport net.minestom.vanilla.datapack.loot.LootTable;\nimport net.minestom.vanilla.datapack.loot.function.LootFunction;\nimport net.minestom.vanilla.datapack.loot.function.Predicate;\nimport net.minestom.vanilla.datapack.recipe.Recipe;\nimport net.minestom.vanilla.datapack.trims.TrimMaterial;\nimport net.minestom.vanilla.datapack.trims.TrimPattern;\nimport net.minestom.vanilla.datapack.worldgen.*;\nimport net.minestom.vanilla.datapack.worldgen.noise.Noise;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.Map;\n\npublic interface Datapack {\n\n    Map<String, NamespacedData> namespacedData();\n\n    static Datapack loadPrimitiveByteArray(FileSystem<byte[]> source) {\n        return loadByteArray(source.map(ByteArray::wrap));\n    }\n\n    static Datapack loadInputStream(FileSystem<InputStream> source) {\n        return loadPrimitiveByteArray(source.map(stream -> {\n            try {\n                return stream.readAllBytes();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        }));\n    }\n\n    static Datapack loadByteArray(FileSystem<ByteArray> source) {\n        return new DatapackLoader().load(source.cache());\n    }\n\n    record McMeta(Pack pack, Filter filter) {\n\n        public McMeta() { // Default\n            this(new Pack(6, \"Minestom Vanilla Datapack\"), new Filter(List.of()));\n        }\n\n        record Pack(int pack_format, String description) {\n        }\n\n        record Filter(List<Pattern> block) {\n            record Pattern(String namespace, String path) {\n            }\n        }\n    }\n\n    record NamespacedData(FileSystem<Advancement> advancements,\n                          FileSystem<McFunction> functions,\n                          FileSystem<LootFunction> item_modifiers,\n                          FileSystem<LootTable> loot_tables,\n                          FileSystem<Predicate> predicates,\n                          FileSystem<Recipe> recipes,\n                          FileSystem<Structure> structures,\n                          FileSystem<ChatType> chat_type,\n                          FileSystem<DamageType> damage_type,\n                          FileSystem<Datapack.Tag> tags,\n                          FileSystem<Dimension> dimensions,\n                          FileSystem<DimensionType> dimension_type,\n                          FileSystem<TrimPattern> trim_pattern,\n                          FileSystem<TrimMaterial> trim_material,\n                          WorldGen world_gen) {\n\n        /**\n         * Performs a deep cache on all of the data.\n         * This helps us load all density functions while in the loading context.\n         */\n        NamespacedData cache() {\n            return new NamespacedData(\n                    advancements.cache(),\n                    functions.cache(),\n                    item_modifiers.cache(),\n                    loot_tables.cache(),\n                    predicates.cache(),\n                    recipes.cache(),\n                    structures.lazy(), // structures may be large, so we don't want to cache them immediately\n                    chat_type.cache(),\n                    damage_type.cache(),\n                    tags.cache(),\n                    dimensions.cache(),\n                    dimension_type.cache(),\n                    trim_pattern.cache(),\n                    trim_material.cache(),\n                    world_gen.cache()\n            );\n        }\n    }\n\n    record McFunction(String source) {\n        public static McFunction fromString(String source) {\n            return new McFunction(source);\n        }\n    }\n\n    record ChatType() {\n        // Undocumented? Couldn't find any info on this\n    }\n\n    record DamageType(String message_id, float exhaustion, String scaling, @Nullable String effects, @Nullable String death_message_type) {\n    }\n\n    record Tag(@Nullable Boolean replace, List<TagValue> values) {\n\n        public Tag {\n            values = List.copyOf(values);\n        }\n\n        public sealed interface TagValue {\n\n            static TagValue fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMap(reader, token -> switch (token) {\n                    case STRING -> DatapackLoader.moshi(ObjectOrTagReference.class);\n                    case BEGIN_OBJECT -> DatapackLoader.moshi(TagEntry.class);\n                    default -> null;\n                });\n            }\n\n            record ObjectOrTagReference(Key tag) implements TagValue {\n                public static ObjectOrTagReference fromJson(JsonReader reader) throws IOException {\n                    return JsonUtils.typeMapMapped(reader, Map.of(\n                            JsonReader.Token.STRING, json -> new ObjectOrTagReference(DatapackLoader.jsonAdaptor(Key.class).fromJson(json))\n                    ));\n                }\n            }\n\n            /**\n             * @param required defaults to true\n             */\n            record TagEntry(ObjectOrTagReference id, @Optional Boolean required) implements TagValue {\n            }\n        }\n    }\n\n    record Dimension(Key type) {\n    }\n\n    record WorldGen(\n            FileSystem<Biome> biome,\n            FileSystem<Carver> configured_carver,\n            FileSystem<ByteArray> configured_feature,\n            FileSystem<DensityFunction> density_function,\n            FileSystem<ByteArray> flat_level_generator_preset,\n            FileSystem<ByteArray> multi_noise_biome_source_parameter_list,\n            FileSystem<Noise> noise,\n            FileSystem<NoiseSettings> noise_settings,\n            FileSystem<ByteArray> placed_feature,\n            FileSystem<ByteArray> processor_list,\n            FileSystem<ByteArray> structure,\n            FileSystem<ByteArray> structure_set,\n            FileSystem<ByteArray> template_pool,\n            FileSystem<ByteArray> world_preset\n            ) {\n        public static WorldGen from(FileSystem<ByteArray> worldgen) {\n            return new WorldGen(\n                    DatapackLoader.parseJsonFolder(worldgen, \"biome\", DatapackLoader.adaptor(Biome.class)),\n                    DatapackLoader.parseJsonFolder(worldgen, \"configured_carver\", DatapackLoader.adaptor(Carver.class)),\n                    worldgen.folder(\"configured_feature\"),\n                    DatapackLoader.parseJsonFolder(worldgen, \"density_function\", DatapackLoader.adaptor(DensityFunction.class)),\n                    worldgen.folder(\"flat_level_generator_preset\"),\n                    worldgen.folder(\"multi_noise_biome_source_parameter_list\"),\n                    DatapackLoader.parseJsonFolder(worldgen, \"noise\", DatapackLoader.adaptor(Noise.class)),\n                    DatapackLoader.parseJsonFolder(worldgen, \"noise_settings\", DatapackLoader.adaptor(NoiseSettings.class)),\n                    worldgen.folder(\"placed_feature\"),\n                    worldgen.folder(\"processor_list\"),\n                    worldgen.folder(\"structure\"),\n                    worldgen.folder(\"structure_set\"),\n                    worldgen.folder(\"template_pool\"),\n                    worldgen.folder(\"world_preset\")\n            );\n        }\n\n        public WorldGen cache() {\n            return new WorldGen(\n                    biome.cache(),\n                    configured_carver.cache(),\n                    configured_feature.cache(),\n                    density_function.cache(),\n                    flat_level_generator_preset.cache(),\n                    multi_noise_biome_source_parameter_list.cache(),\n                    noise.cache(),\n                    noise_settings.cache(),\n                    placed_feature.cache(),\n                    processor_list.cache(),\n                    structure.cache(),\n                    structure_set.cache(),\n                    template_pool.cache(),\n                    world_preset.cache()\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/DatapackLoader.java",
    "content": "package net.minestom.vanilla.datapack;\n\nimport com.squareup.moshi.*;\nimport it.unimi.dsi.fastutil.doubles.DoubleArrayList;\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\nimport it.unimi.dsi.fastutil.doubles.DoubleLists;\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.TagStringIO;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.utils.Range;\nimport net.minestom.vanilla.datapack.advancement.Advancement;\nimport net.minestom.vanilla.datapack.dimension.DimensionType;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.loot.LootTable;\nimport net.minestom.vanilla.datapack.loot.NBTPath;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.datapack.loot.function.LootFunction;\nimport net.minestom.vanilla.datapack.loot.function.Predicate;\nimport net.minestom.vanilla.datapack.number.NumberProvider;\nimport net.minestom.vanilla.datapack.recipe.Recipe;\nimport net.minestom.vanilla.datapack.tags.Tag;\nimport net.minestom.vanilla.datapack.trims.TrimMaterial;\nimport net.minestom.vanilla.datapack.trims.TrimPattern;\nimport net.minestom.vanilla.datapack.worldgen.*;\nimport net.minestom.vanilla.datapack.worldgen.math.CubicSpline;\nimport net.minestom.vanilla.datapack.worldgen.noise.Noise;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static net.minestom.vanilla.datapack.Datapack.*;\n\npublic class DatapackLoader {\n\n    private static final Moshi moshi = createMoshiWithAdaptors();\n\n    DatapackLoader() {\n    }\n\n    private static Moshi createMoshiWithAdaptors() {\n        Moshi.Builder builder = new Moshi.Builder();\n\n        // Native\n        register(builder, UUID.class, DatapackLoader::uuidFromJson);\n\n        // json utils\n        builder.add((type, annotations, moshi) -> {\n            if (typeDoesntMatch(type, JsonUtils.SingleOrList.class)) return null;\n            Type elementType = Types.collectionElementType(type, Collection.class);\n            return new JsonAdapter<JsonUtils.SingleOrList<?>>() {\n                @Override\n                public JsonUtils.SingleOrList<?> fromJson(@NotNull JsonReader reader) throws IOException {\n                    return JsonUtils.SingleOrList.fromJson(elementType, reader);\n                }\n\n                @Override\n                public void toJson(@NotNull JsonWriter writer, JsonUtils.SingleOrList<?> value) {\n                }\n            };\n        });\n\n        // Minestom\n        register(builder, CompoundBinaryTag.class, DatapackLoader::nbtCompoundFromJson);\n        register(builder, Block.class, DatapackLoader::blockFromJson);\n        register(builder, Enchantment.class, DatapackLoader::enchantmentFromJson);\n        register(builder, EntityType.class, DatapackLoader::entityTypeFromJson);\n        register(builder, Material.class, DatapackLoader::materialFromJson);\n        register(builder, Component.class, reader -> {\n            GsonComponentSerializer serializer = GsonComponentSerializer.gson();\n            return serializer.deserialize(reader.nextSource().readUtf8());\n        });\n        register(builder, Key.class, reader -> {\n            String str = reader.nextString();\n            return str.startsWith(\"#\") ? new Tag(str.substring(1)) : Key.key(str);\n        });\n        register(builder, Range.Float.class, DatapackLoader::floatRangeFromJson);\n\n        // Misc\n        register(builder, DoubleList.class, DatapackLoader::doubleListFromJson);\n\n        // VRI Datapack\n        register(builder, Advancement.Trigger.class, Advancement.Trigger::fromJson);\n        register(builder, BlockState.class, BlockState::fromJson);\n        register(builder, LootContext.Trait.class, LootContext.Trait::fromJson);\n        register(builder, LootFunction.class, LootFunction::fromJson);\n        register(builder, Predicate.class, Predicate::fromJson);\n        register(builder, Predicate.BlockStateProperty.Property.class, Predicate.BlockStateProperty.Property::fromJson);\n        register(builder, Predicate.EntityScores.Score.class, Predicate.EntityScores.Score::fromJson);\n        register(builder, Predicate.TimeCheck.Value.class, Predicate.TimeCheck.Value::fromJson);\n        register(builder, Predicate.ValueCheck.Range.class, Predicate.ValueCheck.Range::fromJson);\n        register(builder, NumberProvider.class, NumberProvider.Double::fromJson);\n        register(builder, NumberProvider.Double.class, NumberProvider.Double::fromJson);\n        register(builder, NumberProvider.Int.class, NumberProvider.Int::fromJson);\n        register(builder, LootTable.Pool.Entry.class, LootTable.Pool.Entry::fromJson);\n        register(builder, LootFunction.ApplyBonus.class, LootFunction.ApplyBonus::fromJson);\n        register(builder, LootFunction.CopyNbt.Source.class, LootFunction.CopyNbt.Source::fromJson);\n        register(builder, LootFunction.CopyNbt.Operation.class, LootFunction.CopyNbt.Operation::fromJson);\n        register(builder, LootFunction.LimitCount.Limit.class, LootFunction.LimitCount.Limit::fromJson);\n        register(builder, Recipe.class, Recipe::fromJson);\n        register(builder, Recipe.Ingredient.class, Recipe.Ingredient::fromJson);\n        register(builder, Recipe.Ingredient.Single.class, Recipe.Ingredient.Single::fromJson);\n        register(builder, NBTPath.class, NBTPath::fromJson);\n        register(builder, NBTPath.Single.class, NBTPath.Single::fromJson);\n        register(builder, DensityFunction.class, DensityFunction::fromJson);\n        register(builder, Noise.class, Noise::fromJson);\n        register(builder, NoiseSettings.SurfaceRule.class, NoiseSettings.SurfaceRule::fromJson);\n        register(builder, NoiseSettings.SurfaceRule.SurfaceRuleCondition.class, NoiseSettings.SurfaceRule.SurfaceRuleCondition::fromJson);\n        register(builder, VerticalAnchor.class, VerticalAnchor::fromJson);\n        register(builder, CubicSpline.class, CubicSpline::fromJson);\n        register(builder, DensityFunction.OldBlendedNoise.class, DensityFunction.OldBlendedNoise::fromJson);\n        register(builder, Datapack.Tag.TagValue.class, Datapack.Tag.TagValue::fromJson);\n        register(builder, Datapack.Tag.TagValue.ObjectOrTagReference.class, Datapack.Tag.TagValue.ObjectOrTagReference::fromJson);\n        register(builder, Biome.Effects.Particle.Options.class, Biome.Effects.Particle.Options::fromJson);\n        register(builder, Biome.Sound.class, Biome.Sound::fromJson);\n        register(builder, Carver.class, Carver::fromJson);\n        register(builder, FloatProvider.class, FloatProvider::fromJson);\n        register(builder, Biome.CarversList.class, Biome.CarversList::fromJson);\n        register(builder, Biome.CarversList.Single.class, Biome.CarversList.Single::fromJson);\n        register(builder, HeightProvider.class, HeightProvider::fromJson);\n\n        return builder.build();\n    }\n\n    static <T> FileSystem<T> parseJsonFolder(FileSystem<ByteArray> source, String path, Function<String, T> converter) {\n        return source.folder(path).map(FileSystem.BYTES_TO_STRING).map(converter);\n    }\n\n    public static <T> Function<String, T> adaptor(Class<T> clazz) {\n        return str -> {\n            try {\n                return jsonAdaptor(clazz).fromJson(str);\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        };\n    }\n\n    public static <T> JsonAdapter<T> jsonAdaptor(Class<T> clazz) {\n        return moshi.adapter(clazz);\n    }\n\n    private static final ThreadLocal<LoadingContext> contextPool = new ThreadLocal<>();\n\n    public static LoadingContext loading() {\n        LoadingContext context = contextPool.get();\n        if (context == null) {\n            return STATIC_CONTEXT;\n        }\n        return context;\n    }\n\n    public interface LoadingContext {\n        WorldgenRandom random();\n\n        void whenFinished(Consumer<DatapackFinisher> finishAction);\n\n        default boolean isStatic() {\n            return false;\n        }\n    }\n\n    private static final LoadingContext STATIC_CONTEXT = new LoadingContext() {\n        @Override\n        public WorldgenRandom random() {\n            return WorldgenRandom.xoroshiro(0);\n        }\n\n        @Override\n        public void whenFinished(Consumer<DatapackFinisher> finishAction) {\n            throw new RuntimeException(new IllegalAccessException(\"Not in a datapack loading context\"));\n        }\n\n        @Override\n        public boolean isStatic() {\n            return true;\n        }\n    };\n\n    public interface DatapackFinisher {\n        Datapack datapack();\n    }\n\n    public Datapack load(FileSystem<ByteArray> source) {\n\n        // Default\n        McMeta mcmeta;\n        mcmeta = !source.hasFile(\"pack.mcmeta\") ? new McMeta() : source.map(FileSystem.BYTES_TO_STRING).map(adaptor(McMeta.class)).file(\"pack.mcmeta\");\n        @Nullable ByteArray pack_png = !source.hasFile(\"pack.png\") ? null : source.file(\"pack.png\");\n//        ImageIO.read(pack_png.toStream());\n\n        // Load this datapack on this thread, so that we can use the thread-local contextPool\n        WorldgenRandom loading = WorldgenRandom.xoroshiro(0);\n        Queue<Consumer<DatapackFinisher>> finishers = new ArrayDeque<>();\n        LoadingContext context = new LoadingContext() {\n            @Override\n            public WorldgenRandom random() {\n                return loading;\n            }\n\n            @Override\n            public void whenFinished(Consumer<DatapackFinisher> finishAction) {\n                finishers.add(finishAction);\n            }\n        };\n        contextPool.set(context);\n\n        Map<String, NamespacedData> namespace2data;\n        {\n            namespace2data = new HashMap<>();\n\n            for (String namespace : source.folders()) {\n                FileSystem<ByteArray> dataFolder = source.folder(namespace).inMemory();\n\n                FileSystem<Advancement> advancements = parseJsonFolder(dataFolder, \"advancement\", adaptor(Advancement.class));\n                FileSystem<McFunction> functions = parseJsonFolder(dataFolder, \"functions\", McFunction::fromString);\n                FileSystem<LootFunction> item_modifiers = parseJsonFolder(dataFolder, \"item_modifiers\", adaptor(LootFunction.class));\n                FileSystem<LootTable> loot_tables = parseJsonFolder(dataFolder, \"loot_tables\", adaptor(LootTable.class));\n                FileSystem<Predicate> predicates = parseJsonFolder(dataFolder, \"predicates\", adaptor(Predicate.class));\n                FileSystem<Recipe> recipes = parseJsonFolder(dataFolder, \"recipe\", adaptor(Recipe.class));\n                FileSystem<Structure> structures = dataFolder.folder(\"structures\").map(Structure::fromInput);\n                FileSystem<ChatType> chat_type = parseJsonFolder(dataFolder, \"chat_type\", adaptor(ChatType.class));\n                FileSystem<DamageType> damage_type = parseJsonFolder(dataFolder, \"damage_type\", adaptor(DamageType.class));\n                FileSystem<Datapack.Tag> tags = parseJsonFolder(dataFolder, \"tags\", adaptor(Datapack.Tag.class));\n                FileSystem<Dimension> dimensions = parseJsonFolder(dataFolder, \"dimension\", adaptor(Dimension.class));\n                FileSystem<DimensionType> dimension_type = parseJsonFolder(dataFolder, \"dimension_type\", adaptor(DimensionType.class));\n                FileSystem<TrimPattern> trim_pattern = parseJsonFolder(dataFolder, \"trim_pattern\", adaptor(TrimPattern.class));\n                FileSystem<TrimMaterial> trim_material = parseJsonFolder(dataFolder, \"trim_material\", adaptor(TrimMaterial.class));\n                Datapack.WorldGen world_gen = Datapack.WorldGen.from(dataFolder.folder(\"worldgen\"));\n\n                NamespacedData data = new NamespacedData(advancements, functions, item_modifiers, loot_tables,\n                        predicates, recipes, structures, chat_type, damage_type, tags, dimensions, dimension_type,\n                        trim_pattern, trim_material, world_gen);\n                namespace2data.put(namespace, data);\n            }\n        }\n\n        var copy = namespace2data.entrySet().stream()\n                .map(entry -> Map.entry(entry.getKey(), entry.getValue().cache()))\n                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));\n        Datapack datapack = new Datapack() {\n            @Override\n            public Map<String, NamespacedData> namespacedData() {\n                return copy;\n            }\n\n            @Override\n            public String toString() {\n                return \"Datapack{\" +\n                        \"namespace2data=\" + copy +\n                        '}';\n            }\n        };\n\n        // new we can finish the datapack\n        while (!finishers.isEmpty()) {\n            finishers.poll().accept(() -> datapack);\n        }\n        contextPool.remove();\n        return datapack;\n    }\n\n    private static <T> void register(Moshi.Builder builder, Class<T> clazz, JsonAdapter<T> adapter) {\n        builder.add((type, annotations, moshi) -> {\n            if (typeDoesntMatch(type, clazz)) return null;\n            return adapter;\n        });\n    }\n\n    private static <T> void register(Moshi.Builder builder, Class<T> clazz, IoFunction<JsonReader, T> reader) {\n        register(builder, clazz, new IoJsonAdaptor<>(reader));\n    }\n\n    private static class IoJsonAdaptor <T> extends JsonAdapter<T> {\n        private final IoFunction<JsonReader, T> reader;\n\n        public IoJsonAdaptor(IoFunction<JsonReader, T> reader) {\n            this.reader = reader;\n        }\n\n        @Override\n        public T fromJson(JsonReader jsonReader) throws IOException {\n            return reader.apply(jsonReader);\n        }\n\n        @Override\n        public void toJson(JsonWriter writer, T value) throws IOException {\n        }\n    }\n\n    public interface IoFunction<T, R> {\n        R apply(T t) throws IOException;\n    }\n\n    public static Moshi moshi() {\n        return moshi;\n    }\n\n    public static <T> JsonUtils.IoFunction<JsonReader, T> moshi(Class<? extends T> clazz) {\n        return moshi().adapter(clazz)::fromJson;\n    }\n\n    public static <T> JsonUtils.IoFunction<JsonReader, T> moshi(Type type) {\n        JsonUtils.IoFunction<JsonReader, Object> function = moshi().adapter(type)::fromJson;\n        //noinspection unchecked\n        return (JsonUtils.IoFunction<JsonReader, T>) function;\n    }\n\n    private static CompoundBinaryTag nbtCompoundFromJson(JsonReader reader) throws IOException {\n        String json = reader.nextSource().readUtf8();\n        return TagStringIO.get().asCompound(json);\n    }\n\n    private static Key keyFromJson(JsonReader reader) throws IOException {\n        return Key.key(reader.nextString());\n    }\n\n    private static UUID uuidFromJson(JsonReader reader) throws IOException {\n        throw new UnsupportedOperationException(\"UUIDs are not supported yet\");\n    }\n\n    private static Block blockFromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMapMapped(reader, Map.of(\n                JsonReader.Token.STRING, json -> Block.fromKey(json.nextString()),\n                JsonReader.Token.BEGIN_OBJECT, json -> DatapackLoader.moshi(BlockState.class).apply(json).toMinestom()\n        ));\n    }\n\n    private static Enchantment enchantmentFromJson(JsonReader reader) throws IOException {\n        return MinecraftServer.getEnchantmentRegistry().get(Key.key(reader.nextString()));\n    }\n\n    private static EntityType entityTypeFromJson(JsonReader reader) throws IOException {\n        return EntityType.fromKey(keyFromJson(reader));\n    }\n\n    private static Material materialFromJson(JsonReader reader) throws IOException {\n        Key key = keyFromJson(reader);\n        Material mat = Material.fromKey(key);\n\n        // TODO: Remove these legacy updates\n        Map<Key, Material> legacy = Map.of(\n                Key.key(\"scute\"), Material.TURTLE_SCUTE\n        );\n\n        if (mat == null) {\n            if (legacy.containsKey(key)) {\n                return legacy.get(key);\n            }\n            throw new IllegalStateException(\"Material not found: \" + key);\n        }\n        return mat;\n    }\n\n    private static Range.Float floatRangeFromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMap(reader, token -> switch (token) {\n            case BEGIN_ARRAY -> json -> {\n                    json.beginArray();\n                    var range = new Range.Float((float) json.nextDouble(), (float) json.nextDouble());\n                    json.endArray();\n                    return range;\n                };\n            case NUMBER -> json -> new Range.Float((float) json.nextDouble());\n            default -> null;\n        });\n    }\n\n    private static DoubleList doubleListFromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMap(reader, token -> switch (token) {\n            case BEGIN_ARRAY -> json -> {\n                    json.beginArray();\n                    DoubleList list = new DoubleArrayList();\n                    while (json.peek() == JsonReader.Token.NUMBER) {\n                        list.add(json.nextDouble());\n                    }\n                    json.endArray();\n                    return DoubleLists.unmodifiable(list);\n                };\n            default -> null;\n        });\n    }\n\n    private static final Pattern GENERICS = Pattern.compile(\"<.*>\");\n\n    private static boolean typeDoesntMatch(Type type, Class<?> clazz) {\n        String typeName = GENERICS.matcher(type.getTypeName()).replaceAll(\"\");\n        String clazzName = GENERICS.matcher(clazz.getTypeName()).replaceAll(\"\");\n        return !typeName.equals(clazzName);\n    }\n\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/DatapackLoadingFeature.java",
    "content": "package net.minestom.vanilla.datapack;\n\nimport io.github.pesto.MojangDataFeature;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport net.minestom.vanilla.logging.Loading;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.UnknownNullability;\n\nimport java.util.Objects;\nimport java.util.Set;\n\npublic class DatapackLoadingFeature implements VanillaReimplementation.Feature {\n\n    private @UnknownNullability Datapack datapack;\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n\n        @NotNull MojangDataFeature data = context.vri().feature(MojangDataFeature.class);\n\n        Loading.start(\"Parsing vanilla datapack\");\n        FileSystem<ByteArray> fs = data.latestAssets();\n        datapack = Datapack.loadByteArray(fs);\n        Loading.finish();\n    }\n\n    public @NotNull Datapack current() {\n        Objects.requireNonNull(datapack, \"DatapackLoadingFeature not loaded yet\");\n        return datapack;\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:datapack\");\n    }\n\n    @Override\n    public @NotNull Set<Class<? extends VanillaReimplementation.Feature>> dependencies() {\n        return Set.of(MojangDataFeature.class);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/DatapackUtils.java",
    "content": "package net.minestom.vanilla.datapack;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.tags.Tag;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.noise.Noise;\nimport net.minestom.vanilla.files.FileSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashSet;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\npublic class DatapackUtils {\n    public static Optional<Noise> findNoise(Datapack datapack, String file) {\n        return findInJsonData(file, datapack, data -> data.world_gen().noise());\n    }\n\n    public static Optional<DensityFunction> findDensityFunction(Datapack datapack, String file) {\n        return findInJsonData(file, datapack, data -> data.world_gen().density_function());\n    }\n\n    /** Finds the tag items for the given tag type and namespace ID */\n    public static Set<Key> findTags(Datapack datapack, String tagType, Key namespaceID) {\n        Datapack.NamespacedData data = datapack.namespacedData().get(namespaceID.namespace());\n        if (data == null) return Set.of();\n\n        var itemTags = data.tags().folder(tagType);\n        var itemTag = itemTags.file(namespaceID.value() + \".json\");\n        if (itemTag == null) return Set.of();\n\n        return resolveTagItems(datapack, itemTag);\n    }\n\n\n    private static Set<Key> resolveTagItems(Datapack datapack, Datapack.Tag tag) {\n        Set<Key> materials = new HashSet<>();\n        for (Datapack.Tag.TagValue value : tag.values()) {\n            resolveTagValue(datapack, value, materials::add);\n        }\n        return Set.copyOf(materials);\n    }\n\n    private static void resolveTagValue(Datapack datapack, Datapack.Tag.TagValue value, Consumer<Key> out) {\n        if (value instanceof Datapack.Tag.TagValue.ObjectOrTagReference objectOrTagReference) {\n            if (objectOrTagReference.tag() instanceof Tag tag) {\n                var mats = resolveReferenceTag(datapack, tag);\n                if (mats != null) {\n                    mats.forEach(out);\n                    return;\n                }\n                throw new UnsupportedOperationException(\"Unable to resolve where tag \" + tag + \" is pointing to\");\n            }\n\n            // found the material\n            out.accept(objectOrTagReference.tag());\n            return;\n        }\n        if (value instanceof Datapack.Tag.TagValue.TagEntry tagEntry) {\n            try {\n                resolveTagValue(datapack, tagEntry.id(), out);\n            } catch (UnsupportedOperationException e) {\n                if (tagEntry.required() == null || tagEntry.required()) {\n                    throw e;\n                }\n            }\n            return;\n        }\n        throw new UnsupportedOperationException(\"Unknown tag value type \" + value.getClass().getName());\n    }\n\n    private static @Nullable Set<Key> resolveReferenceTag(Datapack datapack, Key tagNamespace) {\n        // otherwise resolve to another tag\n        for (var entry : datapack.namespacedData().entrySet()) {\n            String namespace = entry.getKey();\n            Datapack.NamespacedData data = entry.getValue();\n            var itemTags = data.tags().folder(\"item\");\n            for (var itemEntry : itemTags.files().stream()\n                    .collect(Collectors.toUnmodifiableMap(Function.identity(), itemTags::file)).entrySet()) {\n                String tagName = itemEntry.getKey().replace(\".json\", \"\");\n                Datapack.Tag itemTag = itemEntry.getValue();\n\n                Key namespacedTag = Key.key(namespace, tagName);\n                if (namespacedTag.equals(tagNamespace)) {\n                    return resolveTagItems(datapack, itemTag);\n                }\n            }\n        }\n\n        return null;\n    }\n\n    private static <T> Optional<T> findInJsonData(String file, Datapack datapack, Function<Datapack.NamespacedData, FileSystem<T>> getFolder) {\n        Key namespaceID = Key.key(file);\n        for (var entry : datapack.namespacedData().entrySet()) {\n\n            // Ensure the namespaces match\n            String namespace = entry.getKey();\n            if (!namespaceID.namespace().equals(namespace)) {\n                continue;\n            }\n\n            // get the folder\n            var data = entry.getValue();\n            FileSystem<T> folder = getFolder.apply(data);\n\n            String targetFile = namespaceID.value();\n            while (targetFile.contains(\"/\")) {\n                String targetFolder = targetFile.substring(0, targetFile.indexOf('/'));\n                targetFile = targetFile.substring(targetFile.indexOf('/') + 1);\n                folder = folder.folder(targetFolder);\n            }\n\n            for (String fileName : folder.files()) {\n                // ensure we are working with a json file\n                if (!fileName.endsWith(\".json\")) {\n                    continue;\n                }\n                String fileId = fileName.substring(0, fileName.length() - 5);\n\n                if (fileId.equals(targetFile)) {\n                    return Optional.of(folder.file(fileName));\n                }\n            }\n        }\n        return Optional.empty();\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/advancement/Advancement.java",
    "content": "package net.minestom.vanilla.datapack.advancement;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\nimport net.minestom.vanilla.datapack.loot.function.Predicate;\nimport net.minestom.vanilla.datapack.tags.ConditionsFor;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\npublic record Advancement(Display display,\n                          @Nullable String parent,\n                          @Nullable Criteria<?> criteria,\n                          @Nullable List<List<String>> requirements,\n                          Rewards rewards) {\n\n\n    public record Display(Display.Icon icon, Component title, @Nullable String frame, String background,\n                          Component description, @Nullable Boolean showToast, @Nullable Boolean announceToChat,\n                          @Nullable Boolean hidden) {\n        public record Icon(Key item, String nbt) {\n        }\n    }\n\n    public record Criteria<TD extends Conditions>(Trigger<TD> trigger, TD conditions) {\n    }\n\n    @SuppressWarnings(\"unused\")\n    public interface Trigger<TD> {\n        static Trigger<?> from(String trigger) {\n            return switch (trigger) {\n                case \"minecraft:allay_drop_item_on_block\" -> ALLAY_DROP_ITEM_ON_BLOCK;\n                case \"minecraft:avoid_vibration\" -> AVOID_VIBRATION;\n                case \"minecraft:bee_nest_destroyed\" -> BEE_NEST_DESTROYED;\n                case \"minecraft:bred_animals\" -> BRED_ANIMALS;\n                case \"minecraft:brewed_potion\" -> BREWED_POTION;\n                case \"minecraft:changed_dimension\" -> CHANGED_DIMENSION;\n                case \"minecraft:channeled_lightning\" -> CHANNELED_LIGHTNING;\n                case \"minecraft:construct_beacon\" -> CONSTRUCT_BEACON;\n                case \"minecraft:consume_item\" -> CONSUME_ITEM;\n                case \"minecraft:cured_zombie_villager\" -> CURED_ZOMBIE_VILLAGER;\n                case \"minecraft:effects_changed\" -> EFFECTS_CHANGED;\n                case \"minecraft:enchanted_item\" -> ENCHANTED_ITEM;\n                case \"minecraft:enter_block\" -> ENTER_BLOCK;\n                case \"minecraft:entity_hurt_player\" -> ENTITY_HURT_PLAYER;\n                case \"minecraft:entity_killed_player\" -> ENTITY_KILLED_PLAYER;\n                case \"minecraft:filled_bucket\" -> FILLED_BUCKET;\n                case \"minecraft:fishing_rod_hooked\" -> FISHING_ROD_HOOKED;\n                case \"minecraft:hero_of_the_village\" -> HERO_OF_THE_VILLAGE;\n                case \"minecraft:impossible\" -> IMPOSSIBLE;\n                case \"minecraft:inventory_changed\" -> INVENTORY_CHANGED;\n                case \"minecraft:item_durability_changed\" -> ITEM_DURABILITY_CHANGED;\n                case \"minecraft:levitation\" -> LEVITATION;\n                case \"minecraft:location\" -> LOCATION;\n                case \"minecraft:nether_travel\" -> NETHER_TRAVEL;\n                case \"minecraft:placed_block\" -> PLACED_BLOCK;\n                case \"minecraft:player_hurt_entity\" -> PLAYER_HURT_ENTITY;\n                case \"minecraft:player_killed_entity\" -> PLAYER_KILLED_ENTITY;\n                case \"minecraft:recipe_unlocked\" -> RECIPE_UNLOCKED;\n                case \"minecraft:shot_crossbow\" -> SHOT_CROSSBOW;\n                case \"minecraft:slept_in_bed\" -> SLEPT_IN_BED;\n                case \"minecraft:summoned_entity\" -> SUMMONED_ENTITY;\n                case \"minecraft:tame_animal\" -> TAME_ANIMAL;\n                case \"minecraft:tick\" -> TICK;\n                case \"minecraft:used_ender_eye\" -> USED_ENDER_EYE;\n                case \"minecraft:used_totem\" -> USED_TOTEM;\n                case \"minecraft:villager_trade\" -> VILLAGER_TRADE;\n                default -> throw new IllegalArgumentException(\"Unknown trigger: \" + trigger);\n            };\n        }\n\n        String trigger();\n\n        // allay_drop_item_on_block\n        Trigger<Conditions.AllayDropItemOnBlock> ALLAY_DROP_ITEM_ON_BLOCK = () -> \"minecraft:allay_drop_item_on_block\";\n\n        // avoid_vibration\n        Trigger<Conditions.AvoidVibration> AVOID_VIBRATION = () -> \"minecraft:avoid_vibration\";\n\n        // bee_nest_destroyed\n        Trigger<Conditions.BeeNestDestroyed> BEE_NEST_DESTROYED = () -> \"minecraft:bee_nest_destroyed\";\n\n        // bred_animals\n        Trigger<Conditions.BredAnimals> BRED_ANIMALS = () -> \"minecraft:bred_animals\";\n\n        // brewed_potion\n        Trigger<Conditions.BrewedPotion> BREWED_POTION = () -> \"minecraft:brewed_potion\";\n\n        // changed_dimension\n        Trigger<Conditions.ChangedDimension> CHANGED_DIMENSION = () -> \"minecraft:changed_dimension\";\n\n        // channeled_lightning\n        Trigger<Conditions.ChanneledLightning> CHANNELED_LIGHTNING = () -> \"minecraft:channeled_lightning\";\n\n        // construct_beacon\n        Trigger<Conditions.ConstructBeacon> CONSTRUCT_BEACON = () -> \"minecraft:construct_beacon\";\n\n        // consume_item\n        Trigger<Conditions.ConsumeItem> CONSUME_ITEM = () -> \"minecraft:consume_item\";\n\n        // cured_zombie_villager\n        Trigger<Conditions.CuredZombieVillager> CURED_ZOMBIE_VILLAGER = () -> \"minecraft:cured_zombie_villager\";\n\n        // effects_changed\n        Trigger<Conditions.EffectsChanged> EFFECTS_CHANGED = () -> \"minecraft:effects_changed\";\n\n        // enchanted_item\n        Trigger<Conditions.EnchantedItem> ENCHANTED_ITEM = () -> \"minecraft:enchanted_item\";\n\n        // enter_block\n        Trigger<Conditions.EnterBlock> ENTER_BLOCK = () -> \"minecraft:enter_block\";\n\n        // entity_hurt_player\n        Trigger<Conditions.EntityHurtPlayer> ENTITY_HURT_PLAYER = () -> \"minecraft:entity_hurt_player\";\n\n        // entity_killed_player\n        Trigger<Conditions.EntityKilledPlayer> ENTITY_KILLED_PLAYER = () -> \"minecraft:entity_killed_player\";\n\n        // fall_from_height\n        Trigger<Conditions.FallFromHeight> FALL_FROM_HEIGHT = () -> \"minecraft:fall_from_height\";\n\n        // filled_bucket\n        Trigger<Conditions.FilledBucket> FILLED_BUCKET = () -> \"minecraft:filled_bucket\";\n\n        // fishing_rod_hooked\n        Trigger<Conditions.FishingRodHooked> FISHING_ROD_HOOKED = () -> \"minecraft:fishing_rod_hooked\";\n\n        // hero_of_the_village\n        Trigger<Conditions.HeroOfTheVillage> HERO_OF_THE_VILLAGE = () -> \"minecraft:hero_of_the_village\";\n\n        // impossible\n        Trigger<Conditions.Impossible> IMPOSSIBLE = () -> \"minecraft:impossible\";\n\n        // inventory_changed\n        Trigger<Conditions.InventoryChanged> INVENTORY_CHANGED = () -> \"minecraft:inventory_changed\";\n\n        // item_durability_changed\n        Trigger<Conditions.ItemDurabilityChanged> ITEM_DURABILITY_CHANGED = () -> \"minecraft:item_durability_changed\";\n\n        // item_used_on_block\n        Trigger<Conditions.ItemUsedOnBlock> ITEM_USED_ON_BLOCK = () -> \"minecraft:item_used_on_block\";\n\n        // kill_mob_near_sculk_catalyst\n        Trigger<Conditions.KillMobNearSculkCatalyst> KILL_MOB_NEAR_SCULK_CATALYST = () -> \"minecraft:kill_mob_near_sculk_catalyst\";\n\n        // killed_by_crossbow\n        Trigger<Conditions.KilledByCrossbow> KILLED_BY_CROSSBOW = () -> \"minecraft:killed_by_crossbow\";\n\n        // levitation\n        Trigger<Conditions.Levitation> LEVITATION = () -> \"minecraft:levitation\";\n\n        // lightning_strike\n        Trigger<Conditions.LightningStrike> LIGHTNING_STRIKE = () -> \"minecraft:lightning_strike\";\n\n        // location\n        Trigger<Conditions.Location> LOCATION = () -> \"minecraft:location\";\n\n        // nether_travel\n        Trigger<Conditions.NetherTravel> NETHER_TRAVEL = () -> \"minecraft:nether_travel\";\n\n        // placed_block\n        Trigger<Conditions.PlacedBlock> PLACED_BLOCK = () -> \"minecraft:placed_block\";\n\n        // player_generates_container_loot\n        Trigger<Conditions.PlayerGeneratesContainerLoot> PLAYER_GENERATES_CONTAINER_LOOT = () -> \"minecraft:player_generates_container_loot\";\n\n        // player_hurt_entity\n        Trigger<Conditions.PlayerHurtEntity> PLAYER_HURT_ENTITY = () -> \"minecraft:player_hurt_entity\";\n\n        // player_interacted_with_entity\n        Trigger<Conditions.PlayerInteractedWithEntity> PLAYER_INTERACTED_WITH_ENTITY = () -> \"minecraft:player_interacted_with_entity\";\n\n        // player_killed_entity\n        Trigger<Conditions.PlayerKilledEntity> PLAYER_KILLED_ENTITY = () -> \"minecraft:player_killed_entity\";\n\n        // recipe_unlocked\n        Trigger<Conditions.RecipeUnlocked> RECIPE_UNLOCKED = () -> \"minecraft:recipe_unlocked\";\n\n        // ride_entity_in_lava\n        Trigger<Conditions.RideEntityInLava> RIDE_ENTITY_IN_LAVA = () -> \"minecraft:ride_entity_in_lava\";\n\n        // shot_crossbow\n        Trigger<Conditions.ShotCrossbow> SHOT_CROSSBOW = () -> \"minecraft:shot_crossbow\";\n\n        // slept_in_bed\n        Trigger<Conditions.SleptInBed> SLEPT_IN_BED = () -> \"minecraft:slept_in_bed\";\n\n        // slide_down_block\n        Trigger<Conditions.SlideDownBlock> SLIDE_DOWN_BLOCK = () -> \"minecraft:slide_down_block\";\n\n        // started_riding\n        Trigger<Conditions.StartedRiding> STARTED_RIDING = () -> \"minecraft:started_riding\";\n\n        // summoned_entity\n        Trigger<Conditions.SummonedEntity> SUMMONED_ENTITY = () -> \"minecraft:summoned_entity\";\n\n        // tame_animal\n        Trigger<Conditions.TameAnimal> TAME_ANIMAL = () -> \"minecraft:tame_animal\";\n\n        // target_hit\n        Trigger<Conditions.TargetHit> TARGET_HIT = () -> \"minecraft:target_hit\";\n\n        // thrown_item_picked_up_by_entity\n        Trigger<Conditions.ThrownItemPickedUpByEntity> THROWN_ITEM_PICKED_UP_BY_ENTITY = () -> \"minecraft:thrown_item_picked_up_by_entity\";\n\n        // thrown_item_picked_up_by_player\n        Trigger<Conditions.ThrownItemPickedUpByPlayer> THROWN_ITEM_PICKED_UP_BY_PLAYER = () -> \"minecraft:thrown_item_picked_up_by_player\";\n\n        // tick\n        Trigger<Conditions.Tick> TICK = () -> \"minecraft:tick\";\n\n        // used_ender_eye\n        Trigger<Conditions.UsedEnderEye> USED_ENDER_EYE = () -> \"minecraft:used_ender_eye\";\n\n        // used_totem\n        Trigger<Conditions.UsedTotem> USED_TOTEM = () -> \"minecraft:used_totem\";\n\n        // using_item\n        Trigger<Conditions.UsingItem> USING_ITEM = () -> \"minecraft:using_item\";\n\n        // villager_trade\n        Trigger<Conditions.VillagerTrade> VILLAGER_TRADE = () -> \"minecraft:villager_trade\";\n\n        // voluntary_exile\n        Trigger<Conditions.VoluntaryExile> VOLUNTARY_EXILE = () -> \"minecraft:voluntary_exile\";\n\n        static Trigger<?> fromJson(JsonReader reader) throws IOException {\n            String trigger = reader.nextString();\n            return Advancement.Trigger.from(trigger);\n        }\n    }\n\n    public sealed interface Conditions {\n\n        // Triggers when an allay drops an item on a block. Available extra conditions:\n        //\n        //     conditions:\n        //         location: The location at the center of the block the item was dropped on.\n        //            Tags common to all locations[\n        //\n        //    ]\n        //\n        // item: The item dropped on the block.\n        //\n        //    All possible conditions for items[\n        //\n        //]\n        record AllayDropItemOnBlock(ConditionsFor.Location location, ConditionsFor.Item item) implements Conditions {\n        }\n\n        // Triggers when a vibration event is ignored because the source player is crouching. No extra conditions.\n        record AvoidVibration() implements Conditions {\n        }\n\n        // Triggers when the player breaks a bee nest or beehive. Available extra conditions:\n        //\n        //     conditions:\n        //         block: Checks the block that was destroyed. Accepts block IDs.\n        //         item: The item used to break the block.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // num_bees_inside: The number of bees that were inside the bee nest/beehive before it was broken.\n        //\n        //     num_bees_inside: Another form for  num_bees_inside.\n        //         max: The maximum value.\n        //         min: The minimum value.\n        record BeeNestDestroyed(Block block, ConditionsFor.Item item, Count num_bees_inside) implements Conditions {\n            public sealed interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n\n        // Triggers after the player breeds 2 animals. Available extra conditions:\n        //\n        //     conditions:\n        //         child: Checks properties of the child that results from the breeding.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // child: Another format for \"child\". Specifies a list of predicates that must pass in order for the criterion to be granted. The origin of the predicate is the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // parent: The parent.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // parent: Another format for \"parent\". Specifies a list of predicates that must pass in order for the criterion to be granted. The origin of the predicate is the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // partner: The partner (The entity the parent was bred with).\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // partner: Another format for \"partner\". Specifies a list of predicates that must pass in order for the criterion to be granted. The origin of the predicate is the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record BredAnimals(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> child,\n                           JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> parent,\n                           JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> partner) implements Conditions {\n        }\n\n        // minecraft:brewed_potion\n        //\n        //Triggers after the player takes any item out of a brewing stand. Available extra conditions:\n        //\n        //     conditions:\n        //         potion: A brewed potion ID.\n        record BrewedPotion(Key potion) implements Conditions {\n        }\n\n        // Triggers after the player travels between two dimensions. Available extra conditions:\n        //\n        //     conditions:\n        //         from: The dimension the entity traveled from. This tag is a resource location for a dimension (only these in vanilla; more can be added with data packs).\n        //         to: The dimension the entity traveled to. Same accepted values as above.\n        record ChangedDimension(Key from, Key to) implements Conditions {\n        }\n\n        // Triggers after the player successfully uses the Channeling enchantment on an entity or a lightning rod. Available extra conditions:\n        //\n        //     conditions:\n        //         victims: The victims hit by the lightning summoned by the Channeling enchantment. All entities in this list must be hit.\n        //            : A victim.\n        //                All possible conditions for entities[\n        //\n        //    ]\n        //\n        //: Another format for the victim. Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the victim hit by the lighting, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record ChanneledLightning(List<JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate>> victims) implements Conditions {\n        }\n\n        // Triggers after the player changes the structure of a beacon. (When the beacon updates itself). Available extra conditions:\n        //\n        //     conditions:\n        //         level: The level of the updated beacon structure.\n        //         level: Another format.\n        //             max: The maximum value.\n        //             min: The minimum value.\n        record ConstructBeacon(Count level) implements Conditions {\n            public sealed interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n        // Triggers when the player consumes an item. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item that was consumed.\n        //            All possible conditions for items[\n        //\n        //]\n        record ConsumeItem(ConditionsFor.Item item) implements Conditions {\n        }\n\n        // Triggers when the player cures a zombie villager. Available extra conditions:\n        //\n        //     conditions:\n        //         villager: The villager that is the result of the conversion. The 'type' tag is redundant since it will always be \"villager\".\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // villager: Another format for \"villager\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the villager, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // zombie: The zombie villager right before the conversion is complete (not when it is initiated). The 'type' tag is redundant since it will always be \"zombie_villager\".\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // zombie: Another format for \"zombie\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the zombie villager, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record CuredZombieVillager(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> villager,\n                                   JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> zombie) implements Conditions {\n        }\n\n        // Triggers after the player gets a status effect applied or taken from them. Available extra conditions:\n        //\n        //     conditions:\n        //         effects: A list of active status effects the player currently has.\n        //             <minecraft:effect_name>: The key name is a status effect name.\n        //                 ambient: Whether the effect is from a beacon.\n        //                 amplifier: The effect amplifier.\n        //                 amplifier: Another format.\n        //                     max: The maximum value.\n        //                     min: The minimum value.\n        //                 duration: The effect duration in ticks.\n        //                 duration: Another format.\n        //                     max: The maximum value.\n        //                     min: The minimum value.\n        //                 visible: Whether the effect has visible particles.\n        //         source: The entity that was the source of the status effect. When there is no entity or when the effect was self-applied or removed, the test passes only if the source is not specified.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // source: Another format for \"source\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the source, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record EffectsChanged(Map<Key, Effect> effects, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> source) implements Conditions {\n\n            public record Effect(boolean ambient, Count amplifier, Count duration, boolean visible) {\n                public interface Count {\n                    record Value(int value) implements Count {\n                    }\n\n                    record Range(int min, int max) implements Count {\n                    }\n                }\n            }\n        }\n\n        // minecraft:enchanted_item\n        //\n        //Triggers after the player enchants an item through an enchanting table (does not get triggered through an anvil, or through commands). Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item after it has been enchanted.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // levels: The levels spent by the player on the enchantment.\n        // levels: Another format.\n        //\n        //     max: The maximum value.\n        //     min: The minimum value.\n        record EnchantedItem(ConditionsFor.Item item, Count levels) implements Conditions {\n            public interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n        // minecraft:enter_block\n        //\n        //Every tick, triggers once for each block the player's hitbox is inside (up to 12 blocks, the maximum number of blocks the player can stand in). Available extra conditions:\n        //\n        //     conditions:\n        //         block: The block that the player is standing in. Accepts block IDs.\n        //         state: A map of block property names to values. Errors if the block doesn't have these properties.\n        //             key: Block property key and value pair.\n        //             key: Another format.\n        //                 max: A maximum value.\n        //                 min: A minimum value.\n        record EnterBlock(Block block, Map<String, Count> state) implements Conditions {\n            public interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n        // minecraft:entity_hurt_player\n        //\n        //Triggers after a player gets hurt, even without a source entity. Available extra conditions:\n        //\n        //     conditions:\n        //         damage: Checks the damage done to the player.\n        //            Damage tags[\n        //\n        //]\n        record EntityHurtPlayer(ConditionsFor.Damage damage) implements Conditions {\n        }\n\n        // minecraft:entity_killed_player\n        //\n        //Triggers after a living entity kills a player. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: Checks the entity that was the source of the damage that killed the player (for example: The skeleton that shot the arrow).\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity that kills the player, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // killing_blow: Checks the type of damage that killed the player.\n        //\n        //    Tags common to all damage types[\n        //\n        //]\n        record EntityKilledPlayer(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity,\n                                  ConditionsFor.DamageTypes killing_blow) implements Conditions {\n        }\n\n        // minecraft:fall_from_height\n        //\n        //Triggers when a player lands after falling. Available extra conditions:\n        //\n        //     conditions:\n        //         start_position: A location predicate for the last position before the falling started.\n        //            Tags common to all locations[\n        //\n        //    ]\n        //\n        // distance: The distance between the start position and the player's position.\n        //\n        //    Distance predicate tags[\n        //\n        //]\n        record FallFromHeight(ConditionsFor.Location start_position, ConditionsFor.Distance distance) implements Conditions {\n        }\n\n        // minecraft:filled_bucket\n        //\n        //Triggers after the player fills a bucket. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item resulting from filling the bucket.\n        //            All possible conditions for items[\n        //\n        //]\n        record FilledBucket(ConditionsFor.Item item) implements Conditions {\n        }\n\n        // minecraft:fishing_rod_hooked\n        //\n        //Triggers after the player successfully catches an item with a fishing rod or pulls an entity with a fishing rod. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: The entity that was pulled, or the fishing bobber if no entity is pulled.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity pulled or the bobber, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // item: The item that was caught.\n        //\n        //    All possible conditions for items[\n        //\n        //    ]\n        //\n        // rod: The fishing rod used.\n        //\n        //    All possible conditions for items[\n        //\n        //]\n        record FishingRodHooked(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity, ConditionsFor.Item item,\n                                ConditionsFor.Item rod) implements Conditions {\n        }\n\n        // minecraft:hero_of_the_village\n        //\n        //Triggers when a raid ends in victory and the player has attacked at least one raider from that raid. No extra conditions.\n        record HeroOfTheVillage() implements Conditions {\n        }\n\n        // minecraft:impossible\n        //\n        //Never triggers. No available conditions.\n        record Impossible() implements Conditions {\n        }\n\n        // minecraft:inventory_changed\n        //\n        //Triggers after any changes happen to the player's inventory. Available extra conditions:\n        //\n        //     conditions:\n        //         items: A list of items in the player's inventory. All items in the list must be in the player's inventory, but not all items in the player's inventory have to be in this list.\n        //            : An item stack.\n        //                All possible conditions for items[\n        //\n        //        ]\n        //\n        // slots:\n        //\n        //     empty: The amount of slots empty in the inventory.\n        //     empty: Another format.\n        //         max: The maximum value.\n        //         min: The minimum value.\n        //     full: The amount of slots completely filled (stacksize) in the inventory.\n        //     full: Another format.\n        //         max: The maximum value.\n        //         min: The minimum value.\n        //     occupied: The amount of slots occupied in the inventory.\n        //     occupied: Another format.\n        //         max: The maximum value.\n        //         min: The minimum value.\n        record InventoryChanged(List<ConditionsFor.Item> items, Slots slots) implements Conditions {\n            public record Slots(Count empty, Count full, Count occupied) {\n                interface Count {\n                    record Value(int value) implements Count {\n                    }\n\n                    record Range(int min, int max) implements Count {\n                    }\n                }\n            }\n        }\n\n        // minecraft:item_durability_changed\n        //\n        //Triggers after any item in the inventory has been damaged in any form. Available extra conditions:\n        //\n        //     conditions:\n        //         delta: The change in durability (negative numbers are used to indicate a decrease in durability).\n        //         delta: Another format.\n        //             max: The maximum value.\n        //             min: The minimum value.\n        //         durability: The remaining durability of the item.\n        //         durability: Another format.\n        //             max: The maximum value.\n        //             min: The minimum value.\n        //         item: The item before it was damaged, allows you to check the durability before the item was damaged.\n        //            All possible conditions for items[\n        //\n        //]\n        record ItemDurabilityChanged(Count delta, Count durability, ConditionsFor.Item item) implements Conditions {\n            public record Count(Count.Value value, Count.Range range) {\n                public record Value(int value) {\n                }\n\n                public record Range(int min, int max) {\n                }\n            }\n        }\n\n        // minecraft:item_used_on_block\n        //\n        //Triggers when the player uses their hand or an item on a block. Available extra conditions:\n        //\n        //     conditions:\n        //         location: The location at the center of the block the item was used on.\n        //            Tags common to all locations[\n        //\n        //    ]\n        //\n        // item: The item used on the block.\n        //\n        //    All possible conditions for items[\n        //\n        //]\n        record ItemUsedOnBlock(ConditionsFor.Location location, ConditionsFor.Item item) implements Conditions {\n        }\n\n        // minecraft:kill_mob_near_sculk_catalyst\n        //\n        //Triggers after a player is the source of a mob or player being killed within the range of a sculk catalyst. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: The entity that was killed.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the mob, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // killing_blow: The type of damage that killed an entity.\n        //\n        //    Tags common to all damage types[\n        //\n        //]\n        record KillMobNearSculkCatalyst(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity,\n                                       ConditionsFor.Damage killing_blow) implements Conditions {\n        }\n\n        // minecraft:killed_by_crossbow\n        //\n        //Triggers after the player kills a mob or player using a crossbow in ranged combat. Available extra conditions:\n        //\n        //     conditions:\n        //         unique_entity_types: The exact count of types of entities killed.\n        //         unique_entity_types: Another format. The acceptable range of count of types of entities killed.\n        //             max: The maximum value.\n        //             min: The minimum value.\n        //         victims: A list of victims. All of the entries must be matched, and one killed entity may match only one entry.\n        //            : A killed entities.\n        //                All possible conditions for entities[\n        //\n        //    ]\n        //\n        //: Another format for the victim. Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the victim, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record KilledByCrossbow(Count unique_entity_types, List<JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate>> victims) implements Conditions {\n            public interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n        // minecraft:levitation\n        //\n        //Triggers when the player has the levitation status effect. Available extra conditions:\n        //\n        //     conditions:\n        //         distance: The distance between the position where the player started levitating and the player's current position.\n        //            Distance predicate tags[\n        //\n        //    ]\n        //\n        // duration: The duration of the levitation in ticks.\n        // duration: Another format.\n        //\n        //     max: The maximum value.\n        //     min: The minimum value.\n        record Levitation(ConditionsFor.Distance distance, Count duration) implements Conditions {\n            public interface Count {\n                record Value(int value) implements Count {\n                }\n\n                record Range(int min, int max) implements Count {\n                }\n            }\n        }\n\n        // minecraft:lightning_strike\n        //\n        //Triggers when a lightning bolt disappears from the world, only for players within a 256 block radius of the lightning bolt. Available extra conditions:\n        //\n        //     conditions:\n        //         lightning: The lightning bolt that disappeared.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // lightning: Another format for \"lightning\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the lightning, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // bystander: An entity not hurt by the lightning strike but in a certain area around it.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // bystander: Another format for \"bystander\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the bystander, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record LightningStrike(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> lightning,\n                               JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> bystander) implements Conditions {\n        }\n\n        // minecraft:location\n        //\n        //Triggers every 20 ticks (1 second). No extra conditions.\n        record Location() implements Conditions {\n        }\n\n        // minecraft:nether_travel\n        //\n        //Triggers when the player travels to the Nether and then returns to the Overworld. Available extra conditions:\n        //\n        //     conditions:\n        //         start_position: A location predicate for the last position before the player teleported to the Nether.\n        //            Tags common to all locations[\n        //\n        //    ]\n        //\n        // distance: The distance between the position where the player teleported to the Nether and the player's position when they returned.\n        //\n        //    Distance predicate tags[\n        //\n        //]\n        record NetherTravel(ConditionsFor.Location start_position, ConditionsFor.Distance distance) implements Conditions {\n        }\n\n        // minecraft:placed_block\n        //\n        //Triggers when the player places a block. Available extra conditions:\n        //\n        //     conditions:\n        //         block: The block that was placed. Accepts block IDs.\n        //         item: The item that was used to place the block before the item was consumed.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // location: The location of the block that was placed.\n        //\n        //    Tags common to all locations[\n        //\n        //    ]\n        //\n        // state: A map of block property names to values. Errors if the block doesn't have these properties.\n        //\n        //     key: Block property key and value pair.\n        //     key: Another format.\n        //         max: A maximum value.\n        //         min: A minimum value.\n        record PlacedBlock(Block block, ConditionsFor.Item item, ConditionsFor.Location location, Map<String, Property> state) implements Conditions {\n            public interface Property {\n                record Value(String value) implements Property {\n                }\n\n                record Range(String min, String max) implements Property {\n                }\n            }\n        }\n\n        // minecraft:player_generates_container_loot\n        //\n        //Triggers when the player generates the contents of a container with a loot table set. Available extra conditions:\n        //\n        //     conditions:\n        //         loot_table*: The resource location of the generated loot table.\n        record PlayerGeneratesContainerLoot(Key loot_table) implements Conditions {\n        }\n\n        // minecraft:player_hurt_entity\n        //\n        //Triggers after the player hurts a mob or player. Available extra conditions:\n        //\n        //     conditions:\n        //         damage: The damage that was dealt.\n        //            Damage tags[\n        //\n        //    ]\n        //\n        // entity: The entity that was damaged.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record PlayerHurtEntity(ConditionsFor.Damage damage, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:player_interacted_with_entity\n        //\n        //Triggers when the player interacts with an entity. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item which was in the player's hand during interaction.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // entity: The entity which was interacted with.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record PlayerInteractedWithEntity(ConditionsFor.Item item, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:player_killed_entity\n        //\n        //Triggers after a player is the source of a mob or player being killed. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: The entity that was killed.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        //\n        // killing_blow: The type of damage that killed an entity.\n        //\n        //    Tags common to all damage types[\n        //\n        //]\n        record PlayerKilledEntity(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity, ConditionsFor.Damage killing_blow) implements Conditions {\n        }\n\n        // minecraft:recipe_unlocked\n        //\n        //Triggers after the player unlocks a recipe (using a knowledge book for example). Available extra conditions:\n        //\n        //     conditions:\n        //         recipe*: The recipe that was unlocked.\n        record RecipeUnlocked(Key recipe) implements Conditions {\n        }\n\n        // minecraft:ride_entity_in_lava\n        //\n        //Triggers when a player mounts an entity walking on lava and while the entity moves with them. Available extra conditions:\n        //\n        //     conditions:\n        //         start_position: A location predicate for the last position before the player mounted the entity.\n        //            Tags common to all locations[\n        //\n        //    ]\n        //\n        // distance: The distance between the start position and the player's position.\n        //\n        //    Distance predicate tags[\n        //\n        //]\n        record RideEntityInLava(ConditionsFor.Location start_position, ConditionsFor.Distance distance) implements Conditions {\n        }\n\n        // minecraft:shot_crossbow\n        //\n        //Triggers when the player shoots a crossbow. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The crossbow that is used.\n        //            All possible conditions for items[\n        //\n        //]\n        record ShotCrossbow(ConditionsFor.Item item) implements Conditions {\n        }\n\n        // minecraft:slept_in_bed\n        //\n        //Triggers when the player enters a bed. No extra conditions.\n        record SleptInBed() implements Conditions {\n        }\n\n        // minecraft:slide_down_block\n        //\n        //Triggers when the player slides down a block. Available extra conditions:\n        //\n        //     conditions:\n        //         block: The block that the player slid on.\n        //         state: A map of block property names to values. Errors if the block doesn't have these properties.\n        //             key: Block property key and value pair.\n        //             key: Another format.\n        //                 max: A maximum value.\n        //                 min: A minimum value.\n        record SlideDownBlock(Block block, Map<String, Property> state) implements Conditions {\n            public interface Property {\n                record Value(String value) implements Property {\n                }\n\n                record Range(String min, String max) implements Property {\n                }\n            }\n        }\n\n        // minecraft:started_riding\n        //\n        //Triggers when the player starts riding a vehicle or an entity starts riding a vehicle currently ridden by the player. No extra conditions.\n        record StartedRiding() implements Conditions {\n        }\n\n        // minecraft:summoned_entity\n        //\n        //Triggers after an entity has been summoned. Works with iron golems (pumpkin and iron blocks), snow golems (pumpkin and snow blocks), the ender dragon (end crystals) and the wither (wither skulls and soul sand/soul soil). Using dispensers, commands, or pistons to place the wither skulls or pumpkins will still activate this trigger. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: The summoned entity.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record SummonedEntity(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:tame_animal\n        //\n        //Triggers after the player tames an animal. Available extra conditions:\n        //\n        //     conditions:\n        //         entity: Checks the entity that was tamed.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record TameAnimal(JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:target_hit\n        //\n        //Triggers when the player shoots a target block. Available extra conditions:\n        //\n        //     conditions:\n        //         signal_strength: The redstone signal that will come out of the target block.\n        //         signal_strength: Another format.\n        //             max: The maximum value.\n        //             min: The minimum value.\n        //         projectile: The projectile hit the target block.\n        //            All possible conditions for entities[\n        //\n        //    ]\n        //\n        // projectile: Another format for \"projectile\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the projectile, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record TargetHit(int signal_strength, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> projectile) implements Conditions {\n        }\n\n        // minecraft:thrown_item_picked_up_by_entity\n        //\n        //Triggers after the player throws an item and another entity picks it up. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The thrown item which was picked up.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // entity: The entity which picked up the item.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record ThrownItemPickedUpByEntity(ConditionsFor.Item item, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:thrown_item_picked_up_by_player\n        //\n        //Triggers when a player picks up an item thrown by another entity. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item thrown.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // entity: The entity that threw the item.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // entity: Another format for \"entity\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the entity, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record ThrownItemPickedUpByPlayer(ConditionsFor.Item item, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> entity) implements Conditions {\n        }\n\n        // minecraft:tick\n        //\n        //Triggers every tick (20 times a second). No extra conditions.\n        record Tick() implements Conditions {\n        }\n\n        // minecraft:used_ender_eye\n        //\n        //Triggers when the player uses an eye of ender (in a world where strongholds generate). Available extra conditions:\n        //\n        //     conditions:\n        //         distance: The horizontal distance between the player and the stronghold.\n        //         distance: Another format.\n        //             max: A maximum value.\n        //             min: A minimum value.\n        record UsedEnderEye(Count distance) implements Conditions {\n            public interface Count {\n                record Value(double value) implements Count {\n                }\n\n                record Range(double min, double max) implements Count {\n                }\n            }\n        }\n\n        // minecraft:used_totem\n        //\n        //Triggers when the player uses a totem. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item, only works with totem items.\n        //            All possible conditions for items[\n        //\n        //]\n        record UsedTotem(ConditionsFor.Item item) implements Conditions {\n        }\n\n        // minecraft:using_item\n        //\n        //Triggers for every tick that the player uses an item that is used continuously. It is known to trigger for bows, crossbows, honey bottles, milk buckets, potions, shields, spyglasses, tridents, food items, eyes of ender, etc. Most items that activate from a single click, such as fishing rods, do not affect this trigger. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item that is used.\n        //            All possible conditions for items[\n        //\n        //]\n        record UsingItem(ConditionsFor.Item item) implements Conditions {\n        }\n\n        // minecraft:villager_trade\n        //\n        //Triggers after the player trades with a villager or a wandering trader. Available extra conditions:\n        //\n        //     conditions:\n        //         item: The item that was purchased. The \"count\" tag checks the count from one trade, not multiple.\n        //            All possible conditions for items[\n        //\n        //    ]\n        //\n        // villager: The villager the item was purchased from.\n        //\n        //    All possible conditions for entities[\n        //\n        //    ]\n        //\n        // villager: Another format for \"villager\". Specifies a list of predicates that must pass in order for the criterion to be granted. The checks are applied to the villager, with the origin being the position of the player that would get the advancement.\n        //\n        //    : A single predicate.\n        record VillagerTrade(ConditionsFor.Item item, JsonUtils.ObjectOrList<ConditionsFor.Entity, Predicate> villager) implements Conditions {\n        }\n\n        // minecraft:voluntary_exile\n        //\n        //Triggers when the player causes a raid. No extra conditions.\n        record VoluntaryExile() implements Conditions {\n        }\n    }\n\n    public record Rewards(List<Key> recipes, List<Key> loot, @Optional Integer experience, String function) {\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/dimension/DimensionType.java",
    "content": "package net.minestom.vanilla.datapack.dimension;\n\nimport net.minestom.vanilla.datapack.number.NumberProvider;\n\n// {\n//  \"ambient_light\": 0.0,\n//  \"bed_works\": true,\n//  \"coordinate_scale\": 1.0,\n//  \"effects\": \"minecraft:overworld\",\n//  \"has_ceiling\": false,\n//  \"has_raids\": true,\n//  \"has_skylight\": true,\n//  \"height\": 384,\n//  \"infiniburn\": \"#minecraft:infiniburn_overworld\",\n//  \"logical_height\": 384,\n//  \"min_y\": -64,\n//  \"monster_spawn_block_light_limit\": 0,\n//  \"monster_spawn_light_level\": {\n//    \"type\": \"minecraft:uniform\",\n//    \"value\": {\n//      \"max_inclusive\": 7,\n//      \"min_inclusive\": 0\n//    }\n//  },\n//  \"natural\": true,\n//  \"piglin_safe\": false,\n//  \"respawn_anchor_works\": false,\n//  \"ultrawarm\": false\n//}\npublic record DimensionType(\n        double ambient_light,\n        boolean bed_works,\n        double coordinate_scale,\n        String effects,\n        boolean has_ceiling,\n        boolean has_raids,\n        boolean has_skylight,\n        int height,\n        String infiniburn,\n        int logical_height,\n        int min_y,\n        int monster_spawn_block_light_limit,\n        NumberProvider.Int monster_spawn_light_level,\n        boolean natural,\n        boolean piglin_safe,\n        boolean respawn_anchor_works,\n        boolean ultrawarm\n) {\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/json/JsonUtils.java",
    "content": "package net.minestom.vanilla.datapack.json;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport okio.Buffer;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class JsonUtils {\n\n    public static JsonReader jsonReader(String source) {\n        try (Buffer buffer = new Buffer().writeUtf8(source)) {\n            return JsonReader.of(buffer);\n        }\n    }\n\n    public interface ObjectOrList<O, E> {\n        /** Throws an exception if this is not an object */\n        boolean isObject();\n        O asObject();\n\n        boolean isList();\n\n        /** Throws an exception if this is not a list */\n        List<E> asList();\n    }\n\n    public interface SingleOrList<T> extends ObjectOrList<T, T>, ListLike<T> {\n        static <T> SingleOrList<T> fromJson(Type elementType, JsonReader reader) throws IOException {\n            JsonReader.Token peek = reader.peek();\n\n            if (peek != JsonReader.Token.BEGIN_ARRAY) {\n                return new Single<>(DatapackLoader.<T>moshi(elementType).apply(reader));\n            }\n\n            Stream.Builder<T> builder = Stream.builder();\n            reader.beginArray();\n            while (reader.hasNext()) {\n                builder.add(DatapackLoader.<T>moshi(elementType).apply(reader));\n            }\n            reader.endArray();\n            return new List<>(builder.build().toList());\n        }\n\n        record Single<O>(O object) implements SingleOrList<O> {\n            @Override\n            public boolean isObject() {\n                return true;\n            }\n\n            @Override\n            public O asObject() {\n                return object;\n            }\n\n            @Override\n            public boolean isList() {\n                return false;\n            }\n\n            @Override\n            public java.util.List<O> asList() {\n                throw new IllegalStateException(\"Not a list\");\n            }\n\n            @Override\n            public java.util.@NotNull List<O> list() {\n                return java.util.List.of(object);\n            }\n        }\n\n        record List<L>(java.util.List<L> list) implements SingleOrList<L> {\n            @Override\n            public boolean isObject() {\n                return false;\n            }\n\n            @Override\n            public L asObject() {\n                throw new IllegalStateException(\"Not an object\");\n            }\n\n            @Override\n            public boolean isList() {\n                return true;\n            }\n\n            @Override\n            public java.util.List<L> asList() {\n                return list;\n            }\n\n            @Override\n            public java.util.@NotNull List<L> list() {\n                return list;\n            }\n        }\n    }\n\n    public interface IoFunction<T, R> {\n        R apply(T t) throws IOException;\n    }\n\n    public static <T> T unionStringType(JsonReader reader, String key, Function<String, IoFunction<JsonReader, T>> findReader) throws IOException {\n        return unionMapType(reader, key, json -> {\n            String value = json.nextString();\n            if (value == null) return null;\n            return Key.key(value).toString();\n        }, findReader);\n    }\n\n    public static <T> T unionStringTypeAdapted(JsonReader reader, String key, Function<String, Class<? extends T>> findReader) throws IOException {\n        return unionStringType(reader, key, str -> {\n            Class<? extends T> clazz = findReader.apply(str);\n            if (clazz == null) return null;\n            return DatapackLoader.moshi(clazz);\n        });\n    }\n\n    public static <T> T unionStringTypeMap(JsonReader reader, String key, Map<String, IoFunction<JsonReader, T>> map) throws IOException {\n        return unionStringType(reader, key, map::get);\n    }\n\n    public static <T> T unionStringTypeMapAdapted(JsonReader reader, String key, Map<String, Class<? extends T>> map) throws IOException {\n        Map<String, IoFunction<JsonReader, T>> adaptedMap = map.entrySet().stream()\n                .map(entry -> {\n                    var entryKey = entry.getKey();\n                    var value = entry.getValue();\n                    return Map.entry(entryKey, DatapackLoader.<T>moshi(value));\n                })\n                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));\n        return unionStringTypeMap(reader, key, adaptedMap);\n    }\n\n    public static <V, T> T unionMapType(JsonReader reader, String key, IoFunction<JsonReader, V> read, Function<V, IoFunction<JsonReader, T>> findReader) throws IOException {\n        // Fetch the property\n        V property;\n        try (JsonReader peek = reader.peekJson()) {\n            peek.beginObject();\n\n            property = JsonUtils.findProperty(peek, key, read);\n            if (property == null)\n                throw new IOException(\"Expected property '\" + key + \"'\");\n        }\n\n\n        // Find the correct handler, and call it\n        IoFunction<JsonReader, T> readFunction = findReader.apply(property);\n        if (readFunction == null)\n            throw new IOException(\"Unknown type: \" + property);\n        return readFunction.apply(reader);\n    }\n\n    public static <T> T typeMapMapped(JsonReader reader, Map<JsonReader.Token, IoFunction<JsonReader, T>> type2readFunction) throws IOException {\n        return typeMap(reader, type2readFunction::get);\n    }\n\n    public static <T> T typeMap(JsonReader reader, IoFunction<JsonReader.Token, IoFunction<JsonReader, T>> type2readFunction) throws IOException {\n        JsonReader.Token token = reader.peek();\n        IoFunction<JsonReader, T> readFunction = type2readFunction.apply(token);\n        if (readFunction == null) throw new IllegalStateException(\"Unknown token type: \" + token);\n        return readFunction.apply(reader);\n    }\n\n    /**\n     * Note that this method MUTATES the reader.\n     * In order to safely use this method, you should call {@link JsonReader#peekJson()} and use that instead.\n     */\n    public static <T> @Nullable T findProperty(JsonReader reader, String property, IoFunction<JsonReader, T> read) throws IOException {\n        while (reader.hasNext() && reader.peek() != JsonReader.Token.END_OBJECT) {\n            String name = reader.nextName();\n            if (name.equals(property)) {\n                return read.apply(reader);\n            } else {\n                reader.skipValue();\n            }\n        }\n        return null;\n    }\n\n    public static boolean hasProperty(JsonReader reader, String property) throws IOException {\n        JsonReader peek = reader.peekJson();\n        while (peek.hasNext() && peek.peek() != JsonReader.Token.END_OBJECT) {\n            String name = peek.nextName();\n            if (name.equals(property)) {\n                return true;\n            } else {\n                peek.skipValue();\n            }\n        }\n        return false;\n    }\n\n    public static <T> Map<String, T> readObjectToMap(JsonReader reader, IoFunction<JsonReader, T> readValue) throws IOException {\n        Map<String, T> map = new HashMap<>();\n        reader.beginObject();\n        while (reader.hasNext() && reader.peek() != JsonReader.Token.END_OBJECT) {\n            String key = reader.nextName();\n            T value = readValue.apply(reader);\n            map.put(key, value);\n        }\n        reader.endObject();\n        return Collections.unmodifiableMap(map);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/json/ListLike.java",
    "content": "package net.minestom.vanilla.datapack.json;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ListIterator;\n\npublic interface ListLike<T> extends List<T> {\n\n    /**\n     * Returns a list containing all of the elements in this list, in proper sequence.\n     */\n    @NotNull List<T> list();\n\n    @Override\n    default int size() {\n        return list().size();\n    }\n\n    @Override\n    default boolean isEmpty() {\n        return list().isEmpty();\n    }\n\n    @Override\n    default boolean contains(Object o) {\n        return list().contains(o);\n    }\n\n    @NotNull\n    @Override\n    default Iterator<T> iterator() {\n        return list().iterator();\n    }\n\n    @NotNull\n    @Override\n    default Object @NotNull [] toArray() {\n        return list().toArray();\n    }\n\n    @NotNull\n    @Override\n    default <T1> T1 @NotNull [] toArray(@NotNull T1 @NotNull [] a) {\n        return list().toArray(a);\n    }\n\n    @Override\n    default boolean add(T t) {\n        return list().add(t);\n    }\n\n    @Override\n    default boolean remove(Object o) {\n        return list().remove(o);\n    }\n\n    @Override\n    default boolean containsAll(@NotNull Collection<?> c) {\n        //noinspection SlowListContainsAll\n        return list().containsAll(c);\n    }\n\n    @Override\n    default boolean addAll(@NotNull Collection<? extends T> c) {\n        return list().addAll(c);\n    }\n\n    @Override\n    default boolean addAll(int index, @NotNull Collection<? extends T> c) {\n        return list().addAll(index, c);\n    }\n\n    @Override\n    default boolean removeAll(@NotNull Collection<?> c) {\n        return list().removeAll(c);\n    }\n\n    @Override\n    default boolean retainAll(@NotNull Collection<?> c) {\n        return list().retainAll(c);\n    }\n\n    @Override\n    default void clear() {\n        list().clear();\n    }\n\n    @Override\n    default T get(int index) {\n        return list().get(index);\n    }\n\n    @Override\n    default T set(int index, T element) {\n        return list().set(index, element);\n    }\n\n    @Override\n    default void add(int index, T element) {\n        list().add(index, element);\n    }\n\n    @Override\n    default T remove(int index) {\n        return list().remove(index);\n    }\n\n    @Override\n    default int indexOf(Object o) {\n        return list().indexOf(o);\n    }\n\n    @Override\n    default int lastIndexOf(Object o) {\n        return list().lastIndexOf(o);\n    }\n\n    @NotNull\n    @Override\n    default ListIterator<T> listIterator() {\n        return list().listIterator();\n    }\n\n    @NotNull\n    @Override\n    default ListIterator<T> listIterator(int index) {\n        return list().listIterator(index);\n    }\n\n    @NotNull\n    @Override\n    default List<T> subList(int fromIndex, int toIndex) {\n        return list().subList(fromIndex, toIndex);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/json/Optional.java",
    "content": "package net.minestom.vanilla.datapack.json;\n\nimport com.squareup.moshi.JsonQualifier;\n\n@JsonQualifier\npublic @interface Optional {\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/LootTable.java",
    "content": "package net.minestom.vanilla.datapack.loot;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.DatapackUtils;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.datapack.loot.function.LootFunction;\nimport net.minestom.vanilla.datapack.loot.function.Predicate;\nimport net.minestom.vanilla.datapack.number.NumberProvider;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Represents a loot table.\n * @param type Specifies the loot context in which the loot table should be invoked. All item modifiers, predicates and number providers are then validated to ensure the parameters of the context type specified here cover all requirements, and prints a warning message in the output log if any modifier or predicate requires a context parameter that is not covered.\n * @param functions Applies item modifiers in order, onto all item stacks dropped by this table.\n * @param pools A list of all pools for this loot table. Pools are applied in order.\n * @param random_sequence A resource location specifying the name of the random sequence that is used to generate loot from this loot table. If only one loot table uses a specific random sequence, the order of the randomized sets of items generated is the same for every world using the same world seed. If multiple loot tables use the same random sequence, the loot generated from any one of them changes depending on how many times and in what order any of the other loot tables were invoked.\n */\npublic record LootTable(@Nullable String type, @Nullable List<LootFunction> functions, @Nullable List<Pool> pools, @Nullable Key random_sequence) {\n    public record Pool(@Nullable List<Predicate> conditions,\n                       @Nullable List<LootFunction> functions,\n                       NumberProvider.Int rolls,\n                       NumberProvider.Double bonus_rolls,\n                       List<Pool.Entry> entries) {\n\n        public sealed interface Entry {\n            @Nullable List<Predicate> conditions();\n\n            Key type();\n\n            static Pool.Entry fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch(type) {\n                    case \"minecraft:item\" -> Pool.Entry.Item.class;\n                    case \"minecraft:tag\" -> Pool.Entry.Tag.class;\n                    case \"minecraft:loot_table\" -> Pool.Entry.LootTableNested.class;\n                    case \"minecraft:dynamic\" -> Pool.Entry.Dynamic.class;\n                    case \"minecraft:empty\" -> Pool.Entry.Empty.class;\n                    case \"minecraft:group\" -> Pool.Entry.Group.class;\n                    case \"minecraft:alternatives\" -> Pool.Entry.Alternatives.class;\n                    case \"minecraft:sequence\" -> Pool.Entry.Sequence.class;\n                    default -> null;\n                });\n            }\n\n            sealed interface ItemGenerator extends Entry {\n                @Nullable List<LootFunction> functions();\n                @Nullable NumberProvider weight();\n                NumberProvider quality();\n\n                /**\n                 * Each element in this list is a singular loot entry.\n                 */\n                List<List<ItemStack>> apply(Datapack datapack, LootContext context);\n            }\n\n            /**\n             * item -> Provides a loot entry that drops a single item stack.\n             * • functions: Invokes item functions to the item stack(s).\n             * - An item function. The JSON structure of this object is described on the Item modifiers page.\n             * • weight: Determines how often the loot entry is chosen out of all the entries in the pool. Entries with higher weights are used more often. The chance of an entry being chosen is [this entry's weight ÷ total of all considered entries' weights].\n             * • quality: Modifies the loot entry's weight based on the luck attribute of the killer_entity for loot context type entity or the this entity for all other loot table types. Formula is floor(weight + (quality × generic.luck)).\n             * • name: The resource location of the item to be generated, e.g. minecraft:diamond. The default, if not changed by item functions, is a stack of 1 of the default instance of the item.\n             */\n            record Item(List<Predicate> conditions,\n                        List<LootFunction> functions,\n                        NumberProvider weight,\n                        NumberProvider quality,\n                        Key name,\n                        @Nullable Integer count) implements ItemGenerator {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:item\");\n                }\n\n                @Override\n                public List<List<ItemStack>> apply(Datapack datapack, LootContext context) {\n                    return List.of(List.of(\n                            ItemStack.of(Objects.requireNonNull(Material.fromKey(name)), count == null ? 1 : count)\n                    ));\n                }\n            }\n\n            /**\n             * tag -> Provides a loot entry that generates all item in an item tag, or multiple loot entries (each entry generates a single item in the item tag).\n             * • functions: Invokes item functions to the item stack(s).\n             * - An item function. The JSON structure of this object is described on the Item modifiers page.\n             * • weight: Determines how often a loot entry is chosen out of all the entries in the pool. Entries with higher weights are used more often. The chance of an entry being chosen is [this entry's weight ÷ total of all considered entries' weights].\n             * • quality: Modifies the loot entry's weight based on the luck attribute of the killer_entity for loot context type entity or the this entity for all other loot table types. Formula is floor(weight + (quality × generic.luck)).\n             * • name: The resource location of the item tag to query, e.g. minecraft:arrows.\n             * • expand: If set to true, provides one loot entry per item in the tag with the same weight and quality, and each entry generates one item. If false, provides a single loot entry that generates all items (each with count of 1) in the tag.\n             */\n            record Tag(List<Predicate> conditions,\n                       List<LootFunction> functions,\n                       NumberProvider weight,\n                       NumberProvider quality,\n                       Key name,\n                       boolean expand) implements ItemGenerator {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:tag\");\n                }\n\n                @Override\n                public List<List<ItemStack>> apply(Datapack datapack, LootContext context) {\n                    List<List<ItemStack>> result = new ArrayList<>();\n\n                    var itemTags = DatapackUtils.findTags(datapack, \"item\", name);\n\n                    var items = itemTags.stream()\n                            .map(Material::fromKey)\n                            .filter(Objects::nonNull)\n                            .map(material -> ItemStack.of(material, 1))\n                            .toList();\n\n                    if (expand) {\n                        for (var item : items) {\n                            result.add(List.of(item));\n                        }\n                    } else {\n                        result.add(items);\n                    }\n\n                    return List.copyOf(result);\n                }\n            }\n\n            /**\n             * loot_table -> Provides another loot table as a loot entry.\n             * • functions: Invokes item functions to the item stack(s).\n             * - An item function. The JSON structure of this object is described on the Item modifiers page.\n             * • weight: Determines how often the loot entry is chosen out of all the entries in the pool. Entries with higher weights are used more often. The chance of an entry being chosen is [this entry's weight ÷ total of all considered entries' weights].\n             * • quality: Modifies the loot entry's weight based on the luck attribute of the killer_entity for loot context type entity or the this entity for all other loot table types. Formula is floor(weight + (quality × generic.luck)).\n             * • name: The resource location of the loot table to be used, e.g. minecraft:gameplay/fishing/junk.\n             */\n            record LootTableNested(List<Predicate> conditions,\n                                   List<LootFunction> functions,\n                                   NumberProvider weight,\n                                   NumberProvider quality,\n                                   Key name) implements Pool.Entry {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:loot_table\");\n                }\n            }\n\n            /**\n             * dynamic -> Provides a loot entry that generates block-specific drops.\n             * • functions: Invokes item functions to the item stack(s).\n             * - An item function. The JSON structure of this object is described on the Item modifiers page.\n             * • weight: Determines how often the loot entry is chosen out of all the entries in the pool. Entries with higher weights are used more often. The chance of an entry being chosen is [this entry's weight ÷ total of all considered entries' weights].\n             * • quality: Modifies the loot entry's weight based on the luck attribute of the killer_entity for loot context type entity or the this entity for all other loot table types. Formula is floor(weight + (quality × generic.luck)).\n             * • name: Can be contents to drop block entity contents.\n             */\n            record Dynamic(List<Predicate> conditions,\n                           List<LootFunction> functions,\n                           NumberProvider weight,\n                           NumberProvider quality,\n                           String name) implements ItemGenerator {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:dynamic\");\n                }\n\n                @Override\n                public List<List<ItemStack>> apply(Datapack datapack, LootContext context) {\n                    Block blockEntity = context.get(LootContext.BLOCK_ENTITY);\n                    // TODO: Drop chest contents\n                    return List.of(List.of());\n                }\n            }\n\n            /**\n             * empty -> Provides a loot entry that generates nothing into the loot pool.\n             * • functions: Invokes item functions to the item stack(s).\n             * - An item function. The JSON structure of this object is described on the Item modifiers page.\n             * • weight: Determines how often the loot entry is chosen out of all the entries in the pool. Entries with higher weights are used more often. The chance of an entry being chosen is [this entry's weight ÷ total of all considered entries' weights].\n             * • quality: Modifies the loot entry's weight based on the luck attribute of the killer_entity for loot context type entity or the this entity for all other loot table types. Formula is floor(weight + (quality × generic.luck)).\n             */\n            record Empty(List<Predicate> conditions,\n                         List<LootFunction> functions,\n                         NumberProvider weight,\n                         NumberProvider quality) implements ItemGenerator {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:empty\");\n                }\n\n                @Override\n                public List<List<ItemStack>> apply(Datapack datapack, LootContext context) {\n                    return List.of(List.of());\n                }\n            }\n\n            /**\n             * group -> All entry providers in the children list is applied into the loot pool. Can be used for convenience, e.g. if one condition applies for multiple entries.\n             * • children: The list of entry providers.\n             * - An entry provider.\n             */\n            record Group(List<Predicate> conditions,\n                         List<Pool.Entry> children) implements Pool.Entry {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:group\");\n                }\n            }\n\n            /**\n             * alternatives -> Only the first successful (conditions are met) entry provider, in order, is applied to the loot pool.\n             * • children: The list of entry providers.\n             * - An entry provider.\n             */\n            record Alternatives(List<Predicate> conditions,\n                                List<Pool.Entry> children) implements Pool.Entry {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:alternatives\");\n                }\n            }\n\n            /**\n             * sequence -> The child entry providers are applied to the loot pool in sequential order, continuing until an entry provider's conditions are not met, then applying no more entry providers from the children.\n             * • children: The list of entry providers.\n             * - An entry provider.\n             */\n            record Sequence(List<Predicate> conditions,\n                            List<Pool.Entry> children) implements Pool.Entry {\n\n                @Override\n                public Key type() {\n                    return Key.key(\"minecraft:sequence\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/NBTPath.java",
    "content": "package net.minestom.vanilla.datapack.loot;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.nbt.BinaryTag;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.util.Map;\n\npublic interface NBTPath {\n\n    static NBTPath fromJson(JsonReader reader) throws IOException {\n        return NBTPathImpl.readPath(new StringReader(reader.nextString()));\n    }\n\n    /**\n     * Indexes the given NBT using this path and returns the results.\n     *\n     * @param nbt the NBT to index\n     * @return a map of the path to the resulting NBT\n     */\n    @NotNull Map<NBTPath.Single, BinaryTag> get(BinaryTag nbt);\n\n    /**\n     * A single path returns either a single NBT value or nothing.\n     * This makes it possible to set a single value in a NBT structure as well.\n     */\n    interface Single extends NBTPath {\n\n        static Single fromJson(JsonReader reader) throws IOException {\n            return NBTPathImpl.readSingle(new StringReader(reader.nextString()));\n        }\n\n        /**\n         * Gets the single result of this path, or returns null if there is not exactly one result.\n         * @param nbt the NBT to index\n         * @return the single result, or null if there is not exactly one result\n         */\n        @Nullable BinaryTag getSingle(BinaryTag nbt);\n\n        @Override\n        @Deprecated\n        default @NotNull Map<Single, BinaryTag> get(BinaryTag nbt) {\n            BinaryTag single = getSingle(nbt);\n            return single == null ? Map.of() : Map.of(this, single);\n        }\n\n        /**\n         * Sets the value of this path in the given NBT to the given value.\n         * @param nbt the NBT to set the value in\n         * @param value the value to set\n         * @return the new NBT, or null if the value could not be set i.e. the path did not exist\n         */\n        @Nullable BinaryTag set(BinaryTag nbt, BinaryTag value);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/NBTPathImpl.java",
    "content": "package net.minestom.vanilla.datapack.loot;\n\nimport it.unimi.dsi.fastutil.ints.IntSet;\nimport net.kyori.adventure.nbt.*;\nimport net.minestom.vanilla.datapack.nbt.NBTUtils;\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.util.*;\nimport java.util.function.BiConsumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.StreamSupport;\n\ninterface NBTPathImpl extends NBTPath {\n\n    static NBTPathImpl readPath(StringReader reader) throws IOException {\n        return Reader.readPath(reader);\n    }\n\n    static Single readSingle(StringReader stringReader) throws IOException {\n        if (!(Reader.readPath(stringReader) instanceof Single single)) {\n            throw new IllegalArgumentException(\"Expected a single nbt path, got a multi nbt path\");\n        }\n        return single;\n    }\n\n    interface NbtPathCollector<T extends BinaryTag> extends BiConsumer<SingleSelector<T>, BinaryTag> {\n    }\n\n    /**\n     * Selects an arbitrary number of elements from a provided NBT element.\n     */\n    interface Selector<T extends BinaryTag> {\n\n        /**\n         * Provides each selected NBT element from {@code source} into {@code selectedElements}.\n         *\n         * @param source           the reference that is the source NBT elemen  t\n         * @param selectedElements the consumer for selected NBT references\n         */\n        void get(@NotNull T source, @NotNull NbtPathCollector<T> selectedElements);\n\n        /**\n         * Used to determine if this selector can be used to select the provided {@code type}.\n         *\n         * @param type the type to check\n         * @return true if this selector can be used to select the provided {@code type}\n         */\n        boolean fitsGeneric(@NotNull BinaryTagType<?> type);\n    }\n\n    /**\n     * Selects a single element (or none) from a provided NBT element.\n     * @param <T>\n     */\n    interface SingleSelector<T extends BinaryTag> extends Selector<T> {\n\n        /**\n         * Provides the selected NBT element from {@code source}.\n         *\n         * @param source the reference that is the source NBT element\n         * @return the selected NBT element\n         */\n        @Nullable BinaryTag get(@NotNull T source);\n\n        @Override\n        default void get(@NotNull T source, @NotNull NbtPathCollector<T> selectedElements) {\n            BinaryTag selected = get(source);\n            if (selected != null) selectedElements.accept(this, selected);\n        }\n    }\n}\n\nrecord NBTPathMultiImpl(@NotNull List<NBTPathImpl.Selector<?>> selectors) implements NBTPathImpl {\n\n    public @NotNull Map<Single, BinaryTag> get(@NotNull BinaryTag source) {\n        Map<List<NBTPathImpl.SingleSelector<?>>, BinaryTag> references = Map.of(List.of(), source);\n        for (var selector : selectors()) {\n            Map<List<NBTPathImpl.SingleSelector<?>>, BinaryTag> newReferences = new HashMap<>();\n\n            references.forEach((list, nbt) -> {\n                if (!selector.fitsGeneric(nbt.type())) return;\n\n                //noinspection unchecked\n                ((NBTPathImpl.Selector<BinaryTag>) selector).get(nbt, (newSelector, newNbt) -> {\n                    List<NBTPathImpl.SingleSelector<?>> newKey = new ArrayList<>(list);\n                    newKey.add(newSelector);\n                    newReferences.put(newKey, newNbt);\n                });\n            });\n\n            if (newReferences.isEmpty()) return Map.of();\n            references = newReferences;\n        }\n        return references.entrySet().stream()\n                .map(entry -> {\n                    NBTPath.Single path = new NBTPathSingleImpl(Collections.unmodifiableList(entry.getKey()));\n                    BinaryTag nbt = entry.getValue();\n                    return Map.entry(path, nbt);\n                }).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));\n    }\n\n    @Override\n    public String toString() {\n        return selectors().stream()\n                .map(Selector::toString)\n                .collect(Collectors.joining());\n    }\n}\n\nrecord NBTPathSingleImpl(List<NBTPathImpl.SingleSelector<?>> selectors) implements NBTPathImpl, NBTPath.Single {\n\n    @Override\n    public @Nullable BinaryTag getSingle(BinaryTag nbt) {\n        for (var selector : selectors()) {\n            if (nbt == null || !selector.fitsGeneric(nbt.type())) {\n                // path has failed\n                return null;\n            }\n            //noinspection unchecked\n            nbt = ((NBTPathImpl.SingleSelector<BinaryTag>) selector).get(nbt);\n        }\n        return nbt;\n    }\n\n    @Override\n    public @Nullable BinaryTag set(BinaryTag nbt, BinaryTag value) {\n        return retrieveModified(0, nbt, value);\n    }\n\n    /**\n     * Retrieves the modified version of this nbt\n     * @param i the index of the selector to use\n     * @param container the container to modify\n     * @param value the value to set\n     * @return the modified version of the container\n     */\n    private @Nullable BinaryTag retrieveModified(int i, BinaryTag container, BinaryTag value) {\n        if (i == selectors.size()) return value;\n        if (container == null) return null;\n\n        SingleSelector<?> selector = selectors.get(i);\n        if (!selector.fitsGeneric(container.type())) return null;\n\n        //noinspection unchecked\n        BinaryTag nbt = ((SingleSelector<BinaryTag>) selector).get(container);\n        if (nbt == null) return null;\n\n        // handle the next id on a per-type basis\n        if (nbt.type() == BinaryTagTypes.COMPOUND) {\n            CompoundBinaryTag compound = (CompoundBinaryTag) nbt;\n\n            String key;\n            { // find the key\n                if (selector instanceof RootKey root) key = root.key();\n                else if (selector instanceof CompoundKey compoundKey) key = compoundKey.key();\n                else if (selector instanceof CompoundFilter) key = null;\n                else throw new IllegalStateException(\"Unknown selector type: \" + selector.getClass());\n            }\n\n            if (key == null) {\n                // key is null, which mean that this is a filter and we can return the next call directly\n                return retrieveModified(i + 1, compound, value);\n            }\n\n            // key is not null, which means that we need to set the value in the compound\n            BinaryTag nextValue = retrieveModified(i + 1, compound.get(key), value);\n            if (nextValue == null) return null;\n            Map<String, BinaryTag> mutCompound = StreamSupport.stream(compound.spliterator(), false)\n                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n            mutCompound.put(key, nextValue);\n            return CompoundBinaryTag.from(mutCompound);\n        }\n\n        if (nbt.type() == BinaryTagTypes.LIST) {\n            ListBinaryTag list = (ListBinaryTag) nbt;\n\n            int index;\n            { // find the index\n                if (selector instanceof ListIndex listIndex) index = listIndex.index();\n                else throw new IllegalStateException(\"Unknown selector type: \" + selector.getClass());\n            }\n\n            if (index < 0 || index >= list.size()) return null;\n\n            BinaryTag nextValue = retrieveModified(i + 1, list.get(index), value);\n            if (nextValue == null) return null;\n\n            List<BinaryTag> javaList = new ArrayList<>(list.stream().toList());\n            javaList.set(index, nextValue);\n            return ListBinaryTag.listBinaryTag(list.elementType(), javaList);\n        }\n\n        // the current nbt container is a value, which means we replace it directly with the value param\n        return value;\n    }\n}\n\n\n// selectors\n\n/**\n * Selects, if possible, the element under the {@link #key()} key of the provided source.<br>\n *\n * @param key the key to select\n */\nrecord RootKey(@NotNull String key) implements NBTPathImpl.SingleSelector<CompoundBinaryTag> {\n\n    @Override\n    public @Nullable BinaryTag get(@NotNull CompoundBinaryTag source) {\n        if (!source.keySet().contains(key)) return null;\n        return source.get(key);\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return type == BinaryTagTypes.COMPOUND;\n    }\n\n    @Override\n    public String toString() {\n        return key;\n    }\n}\n\n/**\n * Selects, if possible, the element under the {@link #key()} key of the provided source.<br>\n *\n * @param key the key to select\n */\nrecord CompoundKey(@NotNull String key) implements NBTPathImpl.SingleSelector<CompoundBinaryTag> {\n\n    @Override\n    public @Nullable BinaryTag get(@NotNull CompoundBinaryTag source) {\n        if (!source.keySet().contains(key)) return null;\n        return source.get(key);\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return type == BinaryTagTypes.COMPOUND;\n    }\n\n    @Contract(pure = true)\n    @Override\n    public @NotNull String toString() {\n        return \".\" + key;\n    }\n}\n\n/**\n * Selects the provided source if it passes the {@link #filter()}.<br>\n *\n * @param filter the filter to use\n */\nrecord CompoundFilter(@NotNull CompoundBinaryTag filter) implements NBTPathImpl.SingleSelector<BinaryTag> {\n\n    @Override\n    public @Nullable BinaryTag get(@NotNull BinaryTag source) {\n        return NBTUtils.compareNBT(filter, source, false) ? source : null;\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return true;\n    }\n\n    @Override\n    public @NotNull String toString() {\n        try {\n            return TagStringIO.get().asString(filter);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n\n/**\n * Selects, if possible, the element of index {@link #index()} from the provided list, or, if the index is negative,\n * selects the nth element from the end of the list, where n is {@link #index()}.<br>\n *\n * @param index the index to select, or negative to select starting from the end\n */\nrecord ListIndex(int index) implements NBTPathImpl.SingleSelector<ListBinaryTag> {\n\n    @Override\n    public @Nullable BinaryTag get(@NotNull ListBinaryTag source) {\n        var newIndex = index >= 0 ? index : source.size() + index;\n        if (newIndex < 0) return null;\n        if (newIndex >= source.size()) return null;\n        return source.get(newIndex);\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return type == BinaryTagTypes.LIST;\n    }\n\n    @Contract(pure = true)\n    @Override\n    public @NotNull String toString() {\n        return \"[\" + index + \"]\";\n    }\n}\n\n/**\n * Selects, if possible, each element from the provided list that fits the {@link #filter()}.<br>\n *\n * @param filter the filter for each element in the list\n */\nrecord ListFilter(@NotNull CompoundBinaryTag filter) implements NBTPathImpl.Selector<ListBinaryTag> {\n\n    @Override\n    public void get(@NotNull ListBinaryTag source, NBTPathImpl.@NotNull NbtPathCollector<ListBinaryTag> selectedElements) {\n        IntStream.range(0, source.size())\n                .mapToObj(i -> Map.entry(i, source.get(i)))\n                .filter(entry -> NBTUtils.compareNBT(filter, entry.getValue(), false))\n                .forEach(entry -> {\n                    int i = entry.getKey();\n                    BinaryTag nbt = entry.getValue();\n                    selectedElements.accept(new ListIndex(i), nbt);\n                });\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return type == BinaryTagTypes.LIST;\n    }\n\n    @Override\n    public @NotNull String toString() {\n        try {\n            return \"[\" + TagStringIO.get().asString(filter) + \"]\";\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n\n/**\n * Selects, if possible, every item from the provided list.<br>\n */\nrecord EntireList() implements NBTPathImpl.Selector<ListBinaryTag> {\n\n    @Override\n    public void get(@NotNull ListBinaryTag source, NBTPathImpl.@NotNull NbtPathCollector<ListBinaryTag> selectedElements) {\n        IntStream.range(0, source.size())\n                .mapToObj(i -> Map.entry(i, source.get(i)))\n                .forEach(entry -> {\n                    int i = entry.getKey();\n                    BinaryTag nbt = entry.getValue();\n                    selectedElements.accept(new ListIndex(i), nbt);\n                });\n    }\n\n    @Override\n    public boolean fitsGeneric(@NotNull BinaryTagType<?> type) {\n        return type == BinaryTagTypes.LIST;\n    }\n\n    @Contract(pure = true)\n    @Override\n    public @NotNull String toString() {\n        return \"[]\";\n    }\n}\n\n// Reading\ninterface Reader {\n\n    @NotNull IntSet VALID_SELECTOR_STARTERS = IntSet.of('.', '{', '[');\n    @NotNull IntSet VALID_INTEGER_CHARACTERS = IntSet.of('-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');\n    @NotNull IntSet INVALID_UNQUOTED_CHARACTERS = IntSet.of(-1, '.', '\\'', '\\\"', '{', '}', '[', ']');\n\n    static @NotNull NBTPathImpl readPath(@NotNull StringReader reader) throws IOException {\n        List<NBTPathImpl.Selector<?>> selectors = new ArrayList<>();\n\n        if (!VALID_SELECTOR_STARTERS.contains(peek(reader))) {\n            var key = readString(reader);\n            if (key != null) {\n                selectors.add(new RootKey(key));\n            }\n        }\n\n        while (true) {\n            reader.mark(0);\n\n            if (!VALID_SELECTOR_STARTERS.contains(peek(reader))) {\n                if (selectors.isEmpty()) {\n                    reader.reset();\n                    String message = \"NBT paths must contain at least one selector (reading from \" + reader + \")\";\n                    throw new IllegalArgumentException(message);\n                }\n                // if all selectors are single, return a NBTPath.Single\n                boolean allSingle = selectors.stream().allMatch(selector -> selector instanceof NBTPathImpl.Single);\n                List<NBTPathImpl.Selector<?>> selectorsView = Collections.unmodifiableList(selectors);\n                //noinspection unchecked\n                return allSingle ? new NBTPathSingleImpl((List<NBTPathImpl.SingleSelector<?>>) (List<?>) selectorsView) : new NBTPathMultiImpl(selectorsView);\n            }\n\n            var selector = readPathSelector(reader);\n            if (selector == null) {\n                reader.reset();\n                String message = \"Invalid NBT path selector (reading from \" + reader + \")\";\n                throw new IllegalArgumentException(message);\n            }\n\n            selectors.add(selector);\n        }\n    }\n\n    // Returning null indicates a failure to read\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    static @Nullable NBTPathImpl.Selector<?> readPathSelector(@NotNull StringReader reader) throws IOException {\n        var firstChar = peek(reader);\n        return switch (firstChar) {\n            case '.' -> {\n                reader.skip(1); // Skip period\n\n                var string = readString(reader);\n                yield string != null ? new CompoundKey(string) : null;\n            }\n            case '{' -> {\n                var compound = NBTUtils.readCompoundSNBT(reader);\n                yield compound != null ? new CompoundFilter(compound) : null;\n            }\n            case '[' -> {\n                reader.skip(1); // Skip opening square brackets\n\n                var secondChar = peek(reader);\n                var selector = switch (secondChar) {\n                    case ']' -> new EntireList();\n                    case '{' -> {\n                        var compound = NBTUtils.readCompoundSNBT(reader);\n                        yield compound != null ? new ListFilter(compound) : null;\n                    }\n                    default -> {\n                        if (VALID_INTEGER_CHARACTERS.contains(secondChar)) {\n                            var index = readInteger(reader);\n                            yield index != null ? new ListIndex(index) : null;\n                        }\n                        yield null;\n                    }\n                };\n\n                reader.skip(1); // Skip closing square brackets\n                yield selector;\n            }\n            default -> null;\n        };\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static @Nullable Integer readInteger(@NotNull StringReader reader) throws IOException {\n        StringBuilder builder = new StringBuilder();\n\n        int peek;\n        while (VALID_INTEGER_CHARACTERS.contains(peek = reader.read())) {\n            builder.appendCodePoint(peek);\n        }\n\n        // Unread the one extra character that was read; this does nothing if the entire string has been read\n        reader.skip(-1);\n\n        try {\n            return Integer.parseInt(builder.toString());\n        } catch (NumberFormatException e) {\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static @Nullable String readString(@NotNull StringReader reader) throws IOException {\n        var peek = peek(reader);\n        if (peek == -1) {\n            return null;\n        }\n\n        StringBuilder builder = new StringBuilder();\n\n        if (peek == '\"' || peek == '\\'') { // Read quoted string\n            reader.skip(1); // Skip the character we know already\n\n            boolean escape = false;\n\n            while (true) {\n                var next = reader.read();\n\n                if (next == '\\\\') { // Read escape character\n                    escape = true;\n                } else if (next == peek && !escape) { // Return if unescaped closing character\n                    return builder.toString();\n                } else {\n                    if (escape) { // If there was an unused escape, re-add it; there's only one character it's used for\n                        builder.appendCodePoint('\\\\');\n                    }\n\n                    if (next == -1) {\n                        return null;\n                    }\n\n                    builder.appendCodePoint(next); // Add the next character always\n                }\n            }\n        }\n\n        // Read unquoted string\n        int read;\n        while (!INVALID_UNQUOTED_CHARACTERS.contains(read = reader.read())) {\n            builder.appendCodePoint(read);\n        }\n\n        // Unread the one extra character that was read; this does nothing if the entire string has been read\n        reader.skip(-1);\n\n        return builder.isEmpty() ? null : builder.toString();\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static int peek(@NotNull StringReader reader) throws IOException {\n        var codePoint = reader.read();\n        reader.skip(-1);\n        return codePoint;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/ContextGroups.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.instance.block.Block;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\npublic class ContextGroups {\n\n    private static final Map<String, LootContext.Trait<Entity>> entityTraits = Set.of(\n                    Traits.THIS,\n                    Traits.DIRECT_KILLER,\n                    Traits.KILLER_ENTITY,\n                    Traits.KILLER_PLAYER.map(entity -> (Entity) entity)\n            ).stream()\n            .map(trait -> Map.entry(trait.id(), trait))\n            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\n    private static final Map<String, LootContext.Trait<Component>> namedTraits = Set.of(\n                    Traits.BLOCK_ENTITY.map(block -> (Component) Component.text(block.name())),\n                    Traits.DIRECT_KILLER.map(entity -> {\n                        @Nullable Component customName = entity.getCustomName();\n                        if (customName != null) return customName;\n                        return Component.text(entity.getEntityType().name());\n                    }),\n                    Traits.KILLER_ENTITY.map(entity -> {\n                        @Nullable Component customName = entity.getCustomName();\n                        if (customName != null) return customName;\n                        return Component.text(entity.getEntityType().name());\n                    }),\n                    Traits.KILLER_PLAYER.map(entity -> {\n                        @Nullable Component customName = entity.getCustomName();\n                        if (customName != null) return customName;\n                        return Component.text(entity.getEntityType().name());\n                    })\n            ).stream()\n            .map(trait -> Map.entry(trait.id(), trait))\n            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\n    private static final Map<String, LootContext.Trait<CompoundBinaryTag>> nbtTraits = Set.of(\n                    Traits.BLOCK_ENTITY.map(Block::nbt),\n                    Traits.THIS.map(entity -> entity.tagHandler().asCompound()),\n                    Traits.KILLER_ENTITY.map(entity -> entity.tagHandler().asCompound()),\n                    Traits.DIRECT_KILLER.map(entity -> entity.tagHandler().asCompound()),\n                    Traits.KILLER_PLAYER.map(entity -> entity.tagHandler().asCompound())\n            ).stream()\n            .map(trait -> Map.entry(trait.id(), trait))\n            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/LootContext.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.function.Function;\n\n// Information Source: https://minecraft.fandom.com/wiki/Loot_table#Loot_context_types\npublic interface LootContext extends Traits {\n\n    // Traits\n    interface Trait<T> {\n        String id();\n\n        Function<Object, @Nullable T> finder();\n\n        default <N> Trait<N> map(Function<T, @Nullable N> mapper) {\n            return new MappedTraitImpl<>(this, mapper);\n        }\n\n        static Trait<?> fromJson(JsonReader reader) throws IOException {\n            // traits are always just the string ids\n            String id = reader.nextString();\n            return Traits.fromId(id);\n        }\n    }\n\n    <T> @Nullable T get(Trait<T> trait);\n\n    default <T> T getOrThrow(Trait<T> trait) {\n        T value = get(trait);\n        if (value == null) throw new IllegalStateException(\"LootContext does not have trait \" + trait.id());\n        return value;\n    }\n\n\n    //     Not used. Supplies no loot context parameters.\n    //    Specifying \"type\":\"empty\" means no context parameters can be used in this loot table.\n    record Empty() implements Util.EmptyLootContext {\n    }\n\n    // Opening of a container with loot table (can be barrel, chest, trapped chest, hopper, minecart with chest,\n    // boat with chest, minecart with hopper, dispenser, dropper, and shulker box).\n    // The command /loot … loot <loot_table>\n    record Chest(Point origin, @Nullable net.minestom.server.entity.Entity entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Chest> traitMap = Util.LootContextTraitMap.<Chest>builder()\n                .put(ORIGIN, Chest::origin)\n                .put(THIS, Chest::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //     Not used for loot table. Specifying \"type\":\"command\" doesn't make sense.\n    //    Used internally by commands such as /item modify or /execute (if|unless) predicate.\n    record Command(Point origin, @Nullable net.minestom.server.entity.Entity entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Command> traitMap = Util.LootContextTraitMap.<Command>builder()\n                .put(ORIGIN, Command::origin)\n                .put(THIS, Command::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //     Not used for loot table. Specifying \"type\":\"selector\" doesn't make sense.\n    //    Used internally by the predicate target selector argument.\n    record Selector(Point origin, net.minestom.server.entity.Entity entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Selector> traitMap = Util.LootContextTraitMap.<Selector>builder()\n                .put(ORIGIN, Selector::origin)\n                .put(THIS, Selector::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //     Fishing.\n    //    The command /loot … fish <loot_table>.\n    record Fishing(Point origin, ItemStack tool,\n                   @Nullable net.minestom.server.entity.Entity entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Fishing> traitMap = Util.LootContextTraitMap.<Fishing>builder()\n                .put(ORIGIN, Fishing::origin)\n                .put(TOOL, Fishing::tool)\n                .put(THIS, Fishing::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //    Loots from a living entity's death.\n    //    The command /loot … kill <target>.\n    record Entity(Point origin, net.minestom.server.entity.Entity entity, DamageType damageSource,\n                  @Nullable net.minestom.server.entity.Entity killer,\n                  @Nullable net.minestom.server.entity.Entity directKiller,\n                  @Nullable net.minestom.server.entity.Player killerPlayer) implements LootContext {\n        private static final Util.LootContextTraitMap<Entity> traitMap = Util.LootContextTraitMap.<Entity>builder()\n                .put(ORIGIN, Entity::origin)\n                .put(THIS, Entity::entity)\n                .put(DAMAGE_SOURCE, Entity::damageSource)\n                .put(KILLER_ENTITY, Entity::killer)\n                .put(DIRECT_KILLER, Entity::directKiller)\n                .put(KILLER_PLAYER, Entity::killerPlayer)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    // Using a brush on suspicious sand that has a loot table.\n    record Archeology(Point origin, @Nullable net.minestom.server.entity.Player entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Archeology> traitMap = Util.LootContextTraitMap.<Archeology>builder()\n                .put(ORIGIN, Archeology::origin)\n                .put(THIS, Archeology::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    // Gift from a cat or villager.\n    record Gift(Point origin, net.minestom.server.entity.Player entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Gift> traitMap = Util.LootContextTraitMap.<Gift>builder()\n                .put(ORIGIN, Gift::origin)\n                .put(THIS, Gift::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //    Bartering with piglins.\n    record Barter(net.minestom.server.entity.Player entity) implements LootContext {\n        private static final Util.LootContextTraitMap<Barter> traitMap = Util.LootContextTraitMap.<Barter>builder()\n                .put(THIS, Barter::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    // Loot table set as an advancement's reward.\n    record AdvancementReward(Point origin, net.minestom.server.entity.Player entity) implements LootContext {\n        private static final Util.LootContextTraitMap<AdvancementReward> traitMap = Util.LootContextTraitMap.<AdvancementReward>builder()\n                .put(ORIGIN, AdvancementReward::origin)\n                .put(THIS, AdvancementReward::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //     Not used for loot table. Specifying \"type\":\"advancement_entity\" doesn't make sense.\n    //    Used internally by an advancement invokes a predicate.\n    record AdvancementEntity(Point origin, net.minestom.server.entity.Player entity) implements LootContext {\n        private static final Util.LootContextTraitMap<AdvancementEntity> traitMap = Util.LootContextTraitMap.<AdvancementEntity>builder()\n                .put(ORIGIN, AdvancementEntity::origin)\n                .put(THIS, AdvancementEntity::entity)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n\n    //     Not used. Supplies all loot context parameters.\n    //    Specifying \"type\":\"generic\" or omitting it means no checking for context parameters in this loot table when loading the data pack.\n    record Generic() implements Util.EmptyLootContext {\n    }\n\n    //     Loots from breaking a block.\n    //    The command /loot … mine <pos>.\n    record Block(net.minestom.server.instance.block.Block blockState, Point origin, ItemStack tool,\n                 @Nullable net.minestom.server.entity.Player entity,\n                 @Nullable net.minestom.server.instance.block.Block blockEntity,\n                 @Nullable Double explosionRadius) implements LootContext {\n        private static final Util.LootContextTraitMap<Block> traitMap = Util.LootContextTraitMap.<Block>builder()\n                .put(BLOCK_STATE, Block::blockState)\n                .put(ORIGIN, Block::origin)\n                .put(TOOL, Block::tool)\n                .put(THIS, Block::entity)\n                .put(BLOCK_ENTITY, Block::blockEntity)\n                .put(EXPLOSION_RADIUS, Block::explosionRadius)\n                .build();\n\n        @Override\n        public <T> @Nullable T get(Trait<T> trait) {\n            return traitMap.obtain(this, trait);\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/MappedTraitImpl.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.Function;\n\npublic record MappedTraitImpl<T, N>(LootContext.Trait<T> trait, Function<T, N> mapper) implements LootContext.Trait<N> {\n    @Override\n    public String id() {\n        return trait.id();\n    }\n\n    @Override\n    public Function<Object, @Nullable N> finder() {\n        return baseValue -> {\n            T value = trait.finder().apply(baseValue);\n            return value == null ? null : mapper.apply(value);\n        };\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/TraitImpl.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.Function;\n\npublic record TraitImpl<T>(String id, Class<T> type) implements LootContext.Trait<T> {\n    @Override\n    public Function<Object, @Nullable T> finder() {\n        return o -> {\n            if (type.isInstance(o)) {\n                return type.cast(o);\n            }\n            return null;\n        };\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/Traits.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\n\ninterface Traits {\n    LootContext.Trait<Block> BLOCK_STATE = new TraitImpl<>(\"block_state\", Block.class);\n\n    LootContext.Trait<Point> ORIGIN = new TraitImpl<>(\"origin\", Point.class);\n\n    LootContext.Trait<DamageType> DAMAGE_SOURCE = new TraitImpl<>(\"damage_source\", DamageType.class);\n\n    LootContext.Trait<Entity> THIS = new TraitImpl<>(\"this\", Entity.class);\n\n    LootContext.Trait<Entity> KILLER_ENTITY = new TraitImpl<>(\"killer\", Entity.class);\n    LootContext.Trait<Player> KILLER_PLAYER = new TraitImpl<>(\"killer_player\", Player.class);\n    LootContext.Trait<Entity> DIRECT_KILLER = new TraitImpl<>(\"direct_killer_entity\", Entity.class);\n\n    LootContext.Trait<ItemStack> TOOL = new TraitImpl<>(\"tool\", ItemStack.class);\n\n    LootContext.Trait<Block> BLOCK_ENTITY = new TraitImpl<>(\"block_entity\", Block.class);\n    LootContext.Trait<Double> EXPLOSION_RADIUS = new TraitImpl<>(\"explosion_radius\", Double.class);\n\n    static LootContext.Trait<?> fromId(String id) {\n        return switch (id) {\n            case \"block_state\" -> BLOCK_STATE;\n            case \"origin\" -> ORIGIN;\n            case \"damage_source\" -> DAMAGE_SOURCE;\n            case \"this\" -> THIS;\n            case \"killer\" -> KILLER_ENTITY;\n            case \"killer_player\" -> KILLER_PLAYER;\n            case \"direct_killer_entity\" -> DIRECT_KILLER;\n            case \"tool\" -> TOOL;\n            case \"block_entity\" -> BLOCK_ENTITY;\n            case \"explosion_radius\" -> EXPLOSION_RADIUS;\n            default -> throw new IllegalArgumentException(\"Unknown trait id: \" + id);\n        };\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/context/Util.java",
    "content": "package net.minestom.vanilla.datapack.loot.context;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Function;\n\nclass Util {\n\n    public interface EmptyLootContext extends LootContext {\n        @Override\n        default <T> @Nullable T get(Trait<T> trait) {\n            return null;\n        }\n    }\n\n    public interface LootContextTraitMap<C extends LootContext> {\n        <T> T obtain(C context, LootContext.Trait<T> trait);\n\n        static <C extends LootContext> Builder<C> builder() {\n            return new BuilderImpl<>();\n        }\n\n        interface Builder<C extends LootContext> {\n            <T> Builder<C> put(LootContext.Trait<T> trait, Function<C, T> value);\n\n            LootContextTraitMap<C> build();\n        }\n    }\n\n    static class BuilderImpl<C extends LootContext> implements LootContextTraitMap.Builder<C> {\n        private final Map<String, Function<C, ?>> map = new HashMap<>();\n\n        @Override\n        public <T> LootContextTraitMap.Builder<C> put(LootContext.Trait<T> trait, Function<C, T> value) {\n            map.put(trait.id(), value);\n            return this;\n        }\n\n        @Override\n        public LootContextTraitMap<C> build() {\n            Map<String, Function<C, ?>> copy = Map.copyOf(this.map);\n            return new LootContextTraitMap<>() {\n                @Override\n                public <T> T obtain(C context, LootContext.Trait<T> trait) {\n                    Object baseValue = copy.get(trait.id()).apply(context);\n                    return trait.finder().apply(baseValue);\n                }\n            };\n        }\n    }\n\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/function/InBuiltLootFunctions.java",
    "content": "package net.minestom.vanilla.datapack.loot.function;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.*;\nimport net.kyori.adventure.text.Component;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.color.DyeColor;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.EquipmentSlotGroup;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.attribute.Attribute;\nimport net.minestom.server.entity.attribute.AttributeOperation;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.component.AttributeList;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.loot.LootTable;\nimport net.minestom.vanilla.datapack.loot.NBTPath;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.datapack.number.NumberProvider;\nimport net.minestom.vanilla.tag.Tags;\nimport net.minestom.vanilla.utils.JavaUtils;\nimport net.minestom.vanilla.utils.MinestomUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.random.RandomGenerator;\n\n@SuppressWarnings(\"unused\")\ninterface InBuiltLootFunctions {\n\n    // Applies a predefined bonus formula to the count of the item stack.\n    interface ApplyBonus extends LootFunction {\n\n        @Override\n        default Key function() {\n            return Key.key(\"minecraft:apply_bonus\");\n        }\n\n        Key enchantment();\n\n        Key formula();\n\n        static ApplyBonus fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.unionStringTypeMapAdapted(reader, \"formula\", Map.of(\n                    \"minecraft:binomial_with_bonus_count\", BinomialWithBonusCount.class,\n                    \"minecraft:ore_drops\", OreDrops.class,\n                    \"minecraft:uniform_bonus_count\", UniformBonusCount.class\n            ));\n        }\n\n        record BinomialWithBonusCount(Key enchantment, Parameters parameters) implements ApplyBonus {\n            @Override\n            public Key formula() {\n                return Key.key(\"minecraft:binomial_with_bonus_count\");\n            }\n\n            public record Parameters(int extra, double probability) {\n            }\n\n            @Override\n            public ItemStack apply(Context context) {\n                int enchantLevel = MinestomUtils.getEnchantLevel(context.itemStack(), enchantment, 0);\n                double n = enchantLevel + parameters().extra();\n\n                RandomGenerator random = context.random();\n                double sum = 0;\n                for (int i = 0; i < n; i++) {\n                    if (random.nextDouble() < parameters().probability()) {\n                        sum++;\n                    }\n                }\n                return context.itemStack().withAmount((int) sum);\n            }\n        }\n\n        record UniformBonusCount(Key enchantment, Parameters parameters) implements ApplyBonus {\n\n            @Override\n            public Key formula() {\n                return Key.key(\"minecraft:uniform_bonus_count\");\n            }\n\n            public record Parameters(double bonusMultiplier) {\n            }\n\n            @Override\n            public ItemStack apply(Context context) {\n                int enchantLevel = MinestomUtils.getEnchantLevel(context.itemStack(), enchantment, 0);\n                double n = enchantLevel * parameters.bonusMultiplier();\n                int count = n == 0.0 ? 0 : (int) context.random().nextDouble(n);\n                return context.itemStack().withAmount(count);\n            }\n        }\n\n        record OreDrops(Key enchantment) implements ApplyBonus {\n\n            @Override\n            public Key formula() {\n                return Key.key(\"minecraft:ore_drops\");\n            }\n\n            @Override\n            public ItemStack apply(Context context) {\n                int enchantLevel = MinestomUtils.getEnchantLevel(context.itemStack(), enchantment, 0);\n                int itemCount = context.itemStack().amount() * (1 + context.random().nextInt(enchantLevel + 2));\n                return context.itemStack().withAmount(itemCount);\n            }\n        }\n    }\n\n    // Copies an entity's or a block entity's name tag into the item's display.Name tag.\n    record CopyName(LootContext.Trait<?> source) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:copy_name\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            Object entityOrBlockEntity = context.getOrThrow(source);\n            @Nullable Component name = null;\n            if (entityOrBlockEntity instanceof Entity entity) {\n                name = entity.getCustomName();\n            }\n            if (entityOrBlockEntity instanceof Block blockEntity) {\n                BlockHandler handler = blockEntity.handler();\n                if (handler == null) return context.itemStack();\n                // TODO: This is not the correct way to get the block entity's name\n                name = Component.text(handler.getKey().value());\n            }\n            if (name == null) return context.itemStack();\n            return context.itemStack().withCustomName(name);\n        }\n    }\n\n    // Copies NBT values from a specified block entity or entity, or from command storage to the item's tag tag.\n    record CopyNbt(Source source, List<Operation> operations) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:copy_nbt\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            BinaryTag sourceNbt = source.nbt(context);\n            BinaryTag itemStackTagNbt = context.itemStack().getTag(Tags.Items.TAG);\n            for (Operation operation : Objects.requireNonNullElse(operations, List.<Operation>of())) {\n                itemStackTagNbt = operation.applyOperation(sourceNbt, itemStackTagNbt);\n            }\n            return context.itemStack().withTag(Tags.Items.TAG, itemStackTagNbt);\n        }\n\n        public sealed interface Source {\n\n            String type();\n\n            BinaryTag nbt(LootFunction.Context context);\n\n            static Source fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.<Source>typeMap(reader, token -> switch (token) {\n                    case STRING -> //noinspection unchecked\n                            json -> new Context(DatapackLoader.moshi(LootContext.Trait.class).apply(json));\n                    case BEGIN_OBJECT -> json -> JsonUtils.unionStringTypeAdapted(json, \"type\", type -> switch(type) {\n                        case \"storage\" -> Storage.class;\n                        case \"context\" -> Context.class;\n                        default -> null;\n                    });\n                    default -> null;\n                });\n            }\n\n            record Context(LootContext.Trait<CompoundBinaryTag> target) implements Source {\n\n                @Override\n                public String type() {\n                    return \"context\";\n                }\n\n                @Override\n                public CompoundBinaryTag nbt(LootFunction.Context context) {\n                    return context.getOrThrow(target);\n                }\n            }\n\n            record Storage(Key storageID) implements Source {\n\n                @Override\n                public String type() {\n                    return \"storage\";\n                }\n\n                @Override\n                public CompoundBinaryTag nbt(LootFunction.Context context) {\n                    // TODO: Fetch command storage?\n                    return CompoundBinaryTag.empty();\n                }\n            }\n        }\n\n        public sealed interface Operation {\n\n            NBTPath.Single source();\n\n            NBTPath.Single target();\n\n            String op();\n\n            BinaryTag apply(BinaryTag source, BinaryTag target);\n\n            default BinaryTag applyOperation(BinaryTag source, BinaryTag itemStackNbt) {\n                BinaryTag sourceNbt = source().getSingle(source);\n                BinaryTag targetNbt = target().getSingle(itemStackNbt);\n                BinaryTag newNbt = apply(sourceNbt, targetNbt);\n                return target().set(itemStackNbt, newNbt);\n            }\n\n            static Operation fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.unionStringTypeAdapted(reader, \"op\", key -> switch(key) {\n                    case \"replace\" -> Replace.class;\n                    case \"merge\" -> Merge.class;\n                    case \"append\" -> Append.class;\n                    default -> null;\n                });\n            }\n\n            record Replace(NBTPath.Single source, NBTPath.Single target) implements Operation {\n                @Override\n                public String op() {\n                    return \"replace\";\n                }\n\n                @Override\n                public BinaryTag apply(BinaryTag source, BinaryTag target) {\n                    return source;\n                }\n            }\n\n            record Merge(NBTPath.Single source, NBTPath.Single target) implements Operation {\n                @Override\n                public String op() {\n                    return \"merge\";\n                }\n\n                @Override\n                public BinaryTag apply(BinaryTag source, BinaryTag target) {\n                    if (!(source instanceof CompoundBinaryTag sourceCompound && target instanceof CompoundBinaryTag targetCompound)) {\n                        throw new IllegalArgumentException(\"Cannot merge non-compound NBT types\");\n                    }\n                    Map<String, BinaryTag> output = new HashMap<>();\n                    for (Map.Entry<String, ? extends BinaryTag> entry : targetCompound) {\n                        output.put(entry.getKey(), entry.getValue());\n                    }\n                    for (Map.Entry<String, ? extends BinaryTag> entry : sourceCompound) {\n                        output.put(entry.getKey(), entry.getValue());\n                    }\n                    return CompoundBinaryTag.from(output);\n                }\n            }\n\n            record Append(NBTPath.Single source, NBTPath.Single target) implements Operation {\n                @Override\n                public String op() {\n                    return \"append\";\n                }\n\n                @Override\n                public BinaryTag apply(BinaryTag source, BinaryTag target) {\n                    if (!(source instanceof ListBinaryTag sourceList && target instanceof ListBinaryTag targetList)) {\n                        throw new IllegalArgumentException(\"Cannot append non-list NBT types\");\n                    }\n\n                    BinaryTagType<?> sourceType = sourceList.elementType();\n                    BinaryTagType<?> targetType = targetList.elementType();\n\n                    if (sourceType != targetType) {\n                        throw new IllegalArgumentException(\"Cannot append lists of different types\");\n                    }\n\n                    List<BinaryTag> values = new ArrayList<>(targetList.stream().toList());\n                    for (BinaryTag nbt : sourceList) {\n                        values.add(nbt);\n                    }\n                    return ListBinaryTag.listBinaryTag(targetType, values);\n                }\n            }\n        }\n    }\n\n    // Copies block state properties provided by loot context to the item's BlockStateTag tag.\n    record CopyState(Block block, List<String> properties) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:copy_state\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            Block blockState = context.getOrThrow(LootContext.BLOCK_STATE);\n            Map<String, String> blockProperties = blockState.properties();\n\n            CompoundBinaryTag nbt = context.itemStack().getTag(Tags.Items.BLOCKSTATE);\n            Map<String, BinaryTag> propertiesMap = new HashMap<>();\n            for (String property : properties) {\n                String value = blockProperties.get(property);\n                if (value == null) {\n                    throw new IllegalArgumentException(\"Block \" + blockState + \" does not have property \" + property);\n                }\n                propertiesMap.put(property, StringBinaryTag.stringBinaryTag(value));\n            }\n            return context.itemStack().withTag(Tags.Items.BLOCKSTATE, CompoundBinaryTag.from(propertiesMap));\n        }\n    }\n\n    // Enchants the item with one randomly-selected enchantment. The power of the enchantment, if applicable, is random.\n    // A book will convert to an enchanted book when enchanted.\n    record EnchantRandomly(List<Enchantment> enchantments) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:enchant_randomly\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.BOOK.equals(itemStack.material())) {\n                itemStack = itemStack.withMaterial(Material.ENCHANTED_BOOK);\n            }\n\n            EnchantmentList list = itemStack.get(DataComponents.ENCHANTMENTS);\n\n            for (Enchantment enchantment : enchantments) {\n                var key = MinecraftServer.getEnchantmentRegistry().getKey(enchantment);\n                if (key == null) {\n                    throw new IllegalArgumentException(\"Invalid enchantment: \" + enchantment);\n                }\n                // random level 1-3\n                int level = context.random().nextInt(1, 4);\n\n                list = list.with(key, level);\n            }\n\n            return itemStack.with(DataComponents.ENCHANTMENTS, list);\n        }\n    }\n\n    // Enchants the item, with the specified enchantment level(roughly equivalent to using an enchantment table at that\n    // level). A book will convert to an enchanted book.\n    record EnchantWithLevels(boolean treasure, NumberProvider levels) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:enchant_with_levels\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.BOOK.equals(itemStack.material())) {\n                itemStack = itemStack.withMaterial(Material.ENCHANTED_BOOK);\n            }\n\n            NumberProvider.Context numberContext = context::random;\n\n            // TODO: Proper enchanting system\n//            int level = levels.asInt().apply(numberContext) / 10;\n//            Enchantment randomEnchant = JavaUtils.randomElement(context.random(), Enchantment.values());\n//\n//            return itemStack.withMeta(builder -> builder.enchantment(randomEnchant, (short) level));\n            return itemStack;\n        }\n    }\n\n    // If the origin is provided by loot context, converts an empty map into an explorer map leading to a nearby\n    // generated structure.\n    record ExplorationMap(@Nullable String destination, @Nullable String decoration, @Nullable Integer zoom,\n                          @Nullable Integer search_radius,\n                          @Nullable Boolean skip_existing_chunks) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:exploration_map\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            String destination = Objects.requireNonNullElse(this.destination, \"on_treasure_maps\");\n            String decoration = Objects.requireNonNullElse(this.decoration, \"mansion\");\n            int zoom = Objects.requireNonNullElse(this.zoom, 2);\n            int searchRadius = Objects.requireNonNullElse(this.search_radius, 50);\n            boolean skipExistingChunks = Objects.requireNonNullElse(this.skip_existing_chunks, true);\n\n            ItemStack itemStack = context.itemStack();\n            if (!Material.MAP.equals(itemStack.material())) {\n                throw new IllegalArgumentException(\"Item stack must be a map\");\n            }\n            itemStack = itemStack.withMaterial(Material.FILLED_MAP);\n\n            // TODO: Exploration maps\n            // converts an empty map into an explorer map leading to a nearby generated structure.\n\n            return itemStack;\n        }\n    }\n\n\n    // Removes some items from a stack, if the explosion ratius is provided by loot context.\n    // Each item in the item stack has a chance of 1/explosion radius to be lost.\n    record ExplosionDecay() implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:explosion_decay\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            double explosionRadius = context.getOrThrow(LootContext.EXPLOSION_RADIUS);\n\n            int itemStackCount = itemStack.amount();\n            int removeAmount = 0;\n\n            for (int i = 0; i < itemStackCount; i++) {\n                if (context.random().nextDouble() < 1 / explosionRadius) {\n                    removeAmount++;\n                }\n            }\n\n            return itemStack.withAmount(itemStackCount - removeAmount);\n        }\n    }\n\n    // Adds required item tags of a player head.\n    record FillPlayerHead(LootContext.Trait<Entity> entity) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:fill_player_head\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (!Material.PLAYER_HEAD.equals(itemStack.material())) {\n                throw new IllegalArgumentException(\"Item stack must be a player head\");\n            }\n\n            Entity entity = context.getOrThrow(this.entity);\n            if (!(entity instanceof Player player)) {\n                throw new IllegalArgumentException(\"Entity must be a player\");\n            }\n\n            // TODO: Player head\n            //noinspection UnstableApiUsage\n//            return itemStack.withMeta(PlayerHeadMeta.class, builder -> builder.skullOwner(player.getUuid()));\n            return itemStack;\n        }\n    }\n\n    // Smelts the item as it would be in a furnace without changing its count.\n    record FurnaceSmelt() implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:furnace_smelt\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            // TODO: Furnace smelting\n            return context.itemStack();\n        }\n    }\n\n    // Limits the count of every item stack.\n    record LimitCount(Limit limit) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:limit_count\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            return limit().limit(context);\n        }\n\n        public interface Limit {\n            ItemStack limit(LootFunction.Context context);\n\n            static Limit fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMapMapped(reader, Map.of(\n                        JsonReader.Token.NUMBER, DatapackLoader.moshi(Constant.class),\n                        JsonReader.Token.BEGIN_OBJECT, DatapackLoader.moshi(MinMax.class)\n                ));\n            }\n\n            record Constant(int limit) implements Limit {\n                @Override\n                public ItemStack limit(LootFunction.Context context) {\n                    return context.itemStack().withAmount(amount -> Math.min(amount, limit));\n                }\n            }\n\n            record MinMax(@Nullable NumberProvider min, @Nullable NumberProvider max) implements Limit {\n                @Override\n                public ItemStack limit(LootFunction.Context context) {\n                    return context.itemStack().withAmount(amount -> {\n                        if (min != null) amount = Math.max(amount, min.asInt().apply(context::random));\n                        if (max != null) amount = Math.min(amount, max.asInt().apply(context::random));\n                        return amount;\n                    });\n                }\n            }\n        }\n    }\n\n    // Adjusts the stack size based on the level of the Looting enchantment on the killer entity provided by loot context.\n    record LootingEnchant(NumberProvider count, @Nullable Integer limit) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:looting_enchant\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            Entity killer = context.getOrThrow(LootContext.KILLER_ENTITY);\n\n            int looting;\n            if (killer instanceof Player player) {\n                ItemStack mainHand = player.getItemInMainHand();\n                int lootingValue = MinestomUtils.getEnchantLevel(mainHand, Enchantment.LOOTING.key(), 0);\n                if (lootingValue == 0) return itemStack;\n                looting = lootingValue;\n            } else {\n                // TODO: Other entities that hold an item\n                return itemStack;\n            }\n\n            double additionalMultipler = count.asDouble().apply(context::random);\n            int additional = (int) Math.floor(looting * additionalMultipler);\n            return context.itemStack().withAmount(amount -> amount + additional);\n        }\n    }\n\n    // Add attribute modifiers to the item.\n    record SetAttributes(List<AttributeModifier> modifiers) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_attributes\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            AttributeList list = context.itemStack().get(DataComponents.ATTRIBUTE_MODIFIERS);\n            if (list == null) list = AttributeList.EMPTY;\n\n            for (AttributeModifier modifier : modifiers()) {\n                list = list.with(modifier.apply(context));\n            }\n            return context.itemStack().with(DataComponents.ATTRIBUTE_MODIFIERS, list);\n        }\n\n        public enum Operation {\n            ADDITION(\"addition\"),\n            MULTIPLY_BASE(\"multiply_base\"),\n            MULTIPLY_TOTAL(\"multiply_total\");\n\n            private final String id;\n\n            Operation(String id) {\n                this.id = id;\n            }\n\n            public String getId() {\n                return id;\n            }\n\n            public AttributeOperation toMinestom() {\n                return switch (this) {\n                    case ADDITION -> AttributeOperation.ADD_VALUE;\n                    case MULTIPLY_BASE -> AttributeOperation.ADD_MULTIPLIED_BASE;\n                    case MULTIPLY_TOTAL -> AttributeOperation.ADD_MULTIPLIED_TOTAL;\n                };\n            }\n        }\n\n        public enum Slot {\n            MAINHAND(\"mainhand\"),\n            OFFHAND(\"offhand\"),\n            FEET(\"feet\"),\n            LEGS(\"legs\"),\n            CHEST(\"chest\"),\n            HEAD(\"head\");\n\n            private final String id;\n\n            Slot(String id) {\n                this.id = id;\n            }\n\n            public String getId() {\n                return id;\n            }\n\n            public EquipmentSlotGroup toMinestom() {\n                return switch (this) {\n                    case MAINHAND -> EquipmentSlotGroup.MAIN_HAND;\n                    case OFFHAND -> EquipmentSlotGroup.OFF_HAND;\n                    case FEET -> EquipmentSlotGroup.FEET;\n                    case LEGS -> EquipmentSlotGroup.LEGS;\n                    case CHEST -> EquipmentSlotGroup.CHEST;\n                    case HEAD -> EquipmentSlotGroup.HEAD;\n                };\n            }\n        }\n\n        public record AttributeModifier(String name, Key attribute, Operation operation, NumberProvider amount,\n                                        @Nullable UUID id, List<Slot> slot) {\n\n            public AttributeModifier(String name, Key attribute, Operation operation, NumberProvider amount,\n                                     @Nullable UUID id, Slot slot) {\n                this(name, attribute, operation, amount, id, List.of(slot));\n            }\n\n            public AttributeList.Modifier apply(Context context) {\n                UUID uuid = Objects.requireNonNullElseGet(id(), UUID::randomUUID);\n                Attribute attribute = Attribute.fromKey(attribute().key());\n                AttributeOperation operation = operation().toMinestom();\n                double amount = amount().asDouble().apply(context::random);\n                net.minestom.server.entity.attribute.AttributeModifier modifier = new net.minestom.server.entity.attribute.AttributeModifier(name(), amount, operation);\n                EquipmentSlotGroup slot = JavaUtils.randomElement(context.random(), slot()).toMinestom();\n\n                if (attribute == null) {\n                    throw new IllegalArgumentException(\"Invalid attribute: \" + attribute());\n                }\n\n                return new AttributeList.Modifier(attribute, modifier, slot);\n            }\n        }\n    }\n\n    // Adds or replaces banner patterns of a banner. Function successfully adds patterns into NBT tag even if invoked on a non-banner.\n    record SetBannerPattern(List<Pattern> patterns, boolean append) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_banner_pattern\");\n        }\n\n        public record Pattern(String pattern, String color) {\n            public Tags.Items.Banner.Pattern toPattern() {\n                int color = DyeColor.valueOf(this.color().toUpperCase(Locale.ROOT)).ordinal();\n                return new Tags.Items.Banner.Pattern(pattern(), color);\n            }\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            List<Tags.Items.Banner.Pattern> patternsList;\n            if (append) {\n                patternsList = new ArrayList<>(itemStack.getTag(Tags.Items.Banner.PATTERNS));\n            } else {\n                patternsList = new ArrayList<>();\n            }\n\n            for (Pattern pattern : patterns()) {\n                patternsList.add(pattern.toPattern());\n            }\n\n            return itemStack.withTag(Tags.Items.Banner.PATTERNS, patternsList);\n        }\n    }\n\n    // Sets the contents of a container block item to a list of entries.\n    record SetContents(List<LootTable.Pool.Entry> entries, EntityType type) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_contents\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            // TODO: Implement\n            return context.itemStack();\n        }\n    }\n\n    // Sets the stack size.\n    record SetCount(NumberProvider count, @Nullable Boolean add) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_count\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            boolean add = Objects.requireNonNullElse(this.add, false);\n\n            int amount = count.asInt().apply(context::random);\n            if (add) {\n                amount += itemStack.amount();\n            } else {\n                amount = Math.min(amount, itemStack.material().maxStackSize());\n            }\n            return itemStack.withAmount(amount);\n        }\n    }\n\n    // Sets the item's damage value (durability).\n    record SetDamage(NumberProvider damage, @Nullable Boolean add) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_damage\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            boolean add = Objects.requireNonNullElse(this.add, false);\n\n            int damage;\n            if (add) {\n                damage = Objects.requireNonNullElse(itemStack.get(DataComponents.DAMAGE), 0) + this.damage.asInt().apply(context::random);\n            } else {\n                damage = this.damage.asInt().apply(context::random);\n            }\n            return itemStack.with(DataComponents.DAMAGE, damage);\n        }\n    }\n\n    // Modifies the item's enchantments. A book will convert to an enchanted book.\n    record SetEnchantments(Map<Enchantment, NumberProvider> enchantments,\n                           @Nullable Boolean add) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_enchantments\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            boolean add = Objects.requireNonNullElse(this.add, false);\n            ItemStack itemStack = context.itemStack();\n\n            EnchantmentList list = itemStack.get(DataComponents.ENCHANTMENTS);\n            if (list == null) list = EnchantmentList.EMPTY;\n\n            for (var entry : enchantments.entrySet()) {\n                Enchantment enchantment = entry.getKey();\n                int count = entry.getValue().asInt().apply(context::random);\n\n                DynamicRegistry.Key<Enchantment> key = MinestomUtils.getEnchantKey(enchantment);\n\n                if (add) {\n                    int previousValue = list.has(key) ? list.level(key) : 0;\n                    int newValue = previousValue + count;\n                    list = list.with(key, newValue);\n                } else {\n                    list = list.with(key, count);\n                }\n            }\n\n            return itemStack.with(DataComponents.ENCHANTMENTS, list);\n        }\n    }\n\n    // Sets the item tags for instrument items to a random value from a tag.\n    record SetInstrument(Key options) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_instrument\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            // TODO: Implement\n            return context.itemStack();\n        }\n    }\n\n    // Sets the loot table for a container block when placed and opened.\n    record SetLootTable(Key name, @Nullable Integer seed, String type) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_loot_table\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            // TODO: Implement\n            return context.itemStack();\n        }\n    }\n\n    // Adds or changes the item's lore.\n    record SetLore(List<Component> lore, String entity, @Nullable Boolean replace) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_lore\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            ItemStack itemStack = context.itemStack();\n            if (Material.AIR.equals(itemStack.material())) {\n                return itemStack;\n            }\n\n            boolean replace = Objects.requireNonNullElse(this.replace, false);\n\n            List<Component> newLore;\n            if (replace) {\n                newLore = lore();\n            } else {\n                List<Component> lore = itemStack.get(DataComponents.LORE);\n                newLore = lore == null ? new ArrayList<>() : new ArrayList<>(lore);\n                newLore.addAll(lore());\n            }\n            return itemStack.with(DataComponents.LORE, newLore);\n        }\n    }\n\n    // Adds or changes the item's custom name.\n    record SetName(Component name, String entity) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_name\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            return context.itemStack().withCustomName(name);\n        }\n    }\n\n    // Adds or changes NBT data of the item.\n    record SetNBT(CompoundBinaryTag nbt) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_nbt\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            CompoundBinaryTag currentNbt = context.itemStack().toItemNBT();\n            CompoundBinaryTag newNbt = CompoundBinaryTag.builder()\n                    .put(currentNbt)\n                    .put(nbt)\n                    .build();\n            return ItemStack.fromItemNBT(newNbt);\n        }\n    }\n\n    // Sets the Potion tag of an item.\n    record SetPotion(Key potion) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_potion\");\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            return context.itemStack().withTag(Tags.Items.Potion.POTION, potion);\n        }\n    }\n\n    // Sets the status effects for suspicious stew. Fails if invoked on an item that is not suspicious stew.\n    record SetStewEffect(List<Effect> effects) implements LootFunction {\n\n        @Override\n        public Key function() {\n            return Key.key(\"minecraft:set_stew_effect\");\n        }\n\n        public record Effect(String type, NumberProvider duration) {\n        }\n\n        @Override\n        public ItemStack apply(Context context) {\n            if (!Material.SUSPICIOUS_STEW.equals(context.itemStack().material())) {\n                throw new IllegalArgumentException(\"Cannot set stew effect on non-stew item\");\n            }\n            // TODO: Implement\n            return context.itemStack();\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/function/InBuiltPredicates.java",
    "content": "package net.minestom.vanilla.datapack.loot.function;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.datapack.number.NumberProvider;\nimport net.minestom.vanilla.datapack.tags.ConditionsFor;\nimport net.minestom.vanilla.utils.MinestomUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ThreadLocalRandom;\n\ninterface InBuiltPredicates {\n\n    /**\n     * alternative—Evaluates a list of predicates and passes if any one of them passes. Invokable from any context.\n     * • terms: The list of predicates to evaluate. A predicate within this array must be an object.\n     * - A predicate, following this structure recursively.\n     */\n    record Alternative(List<Predicate> terms) implements Predicate {\n        @Override\n        public String condition() {\n            return \"alternative\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            for (Predicate term : terms) {\n                if (term.test(context)) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n    /**\n     * block_state_property—Checks the mined block and its block states. Requires block state provided by loot context, and always fails if not provided.\n     * • block: A block ID. The test fails if the block doesn't match.\n     * • properties: (Optional) A map of block state names to values. Errors if the block doesn't have these properties.\n     * • name: A block state and a exact value. The value is a string.\n     * OR\n     * • name: A block state name and a ranged value to match.\n     * • min: The min value.\n     * • max: The max value.\n     */\n    record BlockStateProperty(String block, @Nullable Map<String, Property> properties) implements Predicate {\n        @Override\n        public String condition() {\n            return \"block_state_property\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            if (properties == null) return false;\n\n            Block minedBlock = context.get(LootContext.BLOCK_STATE);\n            if (minedBlock == null) return false;\n\n            for (var entry : properties.entrySet()) {\n                String key = entry.getKey();\n                Property property = entry.getValue();\n                String value = minedBlock.properties().get(key);\n\n                if (!property.test(minedBlock, value)) return false;\n            }\n\n            return true;\n        }\n\n        // if (properties == null) return false;\n\n        public interface Property {\n            boolean test(Block block, String value);\n\n            static Property fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMapMapped(reader, Map.of(\n                        JsonReader.Token.STRING, (JsonUtils.IoFunction<JsonReader, Property>) json -> new Value(json.nextString()),\n                        JsonReader.Token.BEGIN_OBJECT, DatapackLoader.moshi(Range.class)\n                ));\n            }\n\n            record Value(String property) implements Property {\n                @Override\n                public boolean test(Block block, String value) {\n                    // TODO: Test this\n                    return property.equals(value);\n                }\n            }\n\n            record Range(String min, String max) implements Property {\n                @Override\n                public boolean test(Block block, String value) {\n                    // TODO: Implement this somehow... :(\n                    return false;\n                }\n            }\n        }\n    }\n\n    /**\n     * damage_source_properties—Checks properties of damage source. Requires origin and damage source provided by loot context, and always fails if not provided.\n     * • predicate: Predicate applied to the damage source.\n     * - Tags common to all damage types\n     */\n    record DamageSourceProperties(Map<String, Object> predicate) implements Predicate {\n        @Override\n        public String condition() {\n            return \"damage_source_properties\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * entity_properties—Checks properties of an entity. Invokable from any context.\n     * • entity: The entity to check. Specifies an entity from loot context. Can be this, killer, direct_killer, or killer_player.\n     * • predicate: Predicate applied to entity, uses same structure as advancements.\n     * - All possible conditions for entities\n     */\n    record EntityProperties(LootContext.Trait<Entity> entity, Map<String, Object> predicate) implements Predicate {\n        @Override\n        public String condition() {\n            return \"entity_properties\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * entity_scores—Checks the scoreboard scores of an entity. Requires the specified entity provided by loot context, and always fails if not provided.\n     * • entity: The entity to check. Specifies an entity from loot context. Can be this, killer, direct_killer, or killer_player.\n     * • scores: Scores to check. All specified scores must pass for the condition to pass.\n     * • A score: Key name is the objective while the value specifies a range of score values required for the condition to pass.\n     * + min: A number Provider. Minimum score.\n     * + max: A number Provider. Maximum score.\n     * OR\n     * • A score: Shorthand version of the other syntax above, to check the score against a single number only. Key name is the objective while the value is the required score.\n     */\n    record EntityScores(LootContext.Trait<Entity> entity, Map<String, Score> scores) implements Predicate {\n        @Override\n        public String condition() {\n            return \"entity_scores\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n\n        public sealed interface Score {\n            boolean test();\n\n            static Score fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMapMapped(reader, Map.of(\n                        JsonReader.Token.NUMBER, DatapackLoader.moshi(Value.class),\n                        JsonReader.Token.BEGIN_OBJECT, DatapackLoader.moshi(Range.class)\n                ));\n            }\n\n            record Value(int value) implements Score {\n                @Override\n                public boolean test() {\n                    return false;\n                }\n            }\n\n            record Range(NumberProvider min, NumberProvider max) implements Score {\n                @Override\n                public boolean test() {\n                    return false;\n                }\n            }\n        }\n    }\n\n    /**\n     * inverted—Inverts another loot table condition. Invokable from any context.\n     * • term: The condition to be negated, following the same structure as outlined here, recursively.\n     */\n    record Inverted(Predicate term) implements Predicate {\n        @Override\n        public String condition() {\n            return \"inverted\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            return !term.test(context);\n        }\n    }\n\n    /**\n     * killed_by_player—Checks if there is a killer_player entity provided by loot context. Requires killer_player entity provided by loot context, and always fails if not provided.\n     */\n    record KilledByPlayer() implements Predicate {\n        @Override\n        public String condition() {\n            return \"killed_by_player\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            return context.get(LootContext.KILLER_PLAYER) != null;\n        }\n    }\n\n    /**\n     * location_check—Checks the current location against location criteria. Requires origin provided by loot context, and always fails if not provided.\n     * • offsetX - optional offsets to location\n     * • offsetY - optional offsets to location\n     * • offsetZ - optional offsets to location\n     * • predicate: Predicate applied to location, uses same structure as advancements.\n     * - Tags common to all locations\n     */\n    record LocationCheck(@Optional Integer offsetX, @Optional Integer offsetY, @Optional Integer offsetZ,\n                         ConditionsFor.Location predicate) implements Predicate {\n        @Override\n        public String condition() {\n            return \"location_check\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * match_tool—Checks tool used to mine the block. Requires tool provided by loot context, and always fails if not provided.\n     * • predicate: Predicate applied to item, uses same structure as advancements.\n     * - All possible conditions for items\n     */\n    record MatchTool(ConditionsFor.Item predicate) implements Predicate {\n        @Override\n        public String condition() {\n            return \"match_tool\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * random_chance—Generates a random number between 0.0 and 1.0, and checks if it is less than a specified value. Invokable from any context.\n     * • chance: Success rate as a number 0.0–1.0.\n     */\n    record RandomChance(float chance) implements Predicate {\n        @Override\n        public String condition() {\n            return \"random_chance\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            return ThreadLocalRandom.current().nextFloat() < chance;\n        }\n    }\n\n    /**\n     * random_chance_with_looting—Generates a random number between 0.0 and 1.0, and checks if it is less than a specified value which has been affected by the level of Looting on the killer entity. Requires killer entity provided by loot context, and if not provided, the looting level is regarded as 0.\n     * • chance: Base success rate.\n     * • looting_multiplier: Looting adjustment to the base success rate. Formula is chance + (looting_level * looting_multiplier).\n     */\n    record RandomChanceWithLooting(float chance, float looting_multiplier) implements Predicate {\n        @Override\n        public String condition() {\n            return \"random_chance_with_looting\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            double random = ThreadLocalRandom.current().nextDouble();\n\n            int looting = 0;\n\n            Player player = context.get(LootContext.KILLER_PLAYER);\n            if (player != null) {\n                EnchantmentList enchants = player.getItemInMainHand().get(DataComponents.ENCHANTMENTS);\n                if (enchants != null && enchants.has(Enchantment.LOOTING)) {\n                    looting = enchants.level(Enchantment.LOOTING);\n                }\n            }\n\n            return random < chance + (looting * looting_multiplier);\n        }\n    }\n\n    /**\n     * reference—Invokes a predicate file and returns its result. Invokable from any context.\n     * • name: The resource location of the predicate to invoke. A cyclic reference causes a parsing failure.\n     */\n    record Reference(String name) implements Predicate {\n        @Override\n        public String condition() {\n            return \"reference\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * survives_explosion—Returns success with 1 ÷ explosion radius probability. Requires explosion radius provided by loot context, and always success if not provided.\n     */\n    record SurvivesExplosion() implements Predicate {\n        @Override\n        public String condition() {\n            return \"survives_explosion\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            Double explosionRadius = context.get(LootContext.EXPLOSION_RADIUS);\n            if (explosionRadius == null) return true;\n            return ThreadLocalRandom.current().nextFloat() < 1.0 / explosionRadius;\n        }\n    }\n\n    /**\n     * table_bonus—Passes with probability picked from a list, indexed by enchantment power. Requires tool provided by loot context. If not provided, the enchantment level is regarded as 0.\n     * • enchantment: Resource location of enchantment.\n     * • chances: List of probabilities for enchantment power, indexed from 0.\n     */\n    record TableBonus(Key enchantment, List<Float> chances) implements Predicate {\n        @Override\n        public String condition() {\n            return \"table_bonus\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            ItemStack item = context.getOrThrow(LootContext.TOOL);\n            EnchantmentList enchants = item.get(DataComponents.ENCHANTMENTS);\n            DynamicRegistry.Key<Enchantment> enchantment = MinestomUtils.getEnchantKey(this.enchantment);\n            int level = enchants == null || !enchants.has(enchantment) ? 0 : enchants.level(enchantment);\n\n            return ThreadLocalRandom.current().nextFloat() < chances.get(level);\n        }\n    }\n\n    /**\n     * time_check—Compares the current day time (or rather, 24000 * day count + day time) against given values. Invokable from any context.\n     * • value: The time to compare the day time against.\n     * • min: A number Provider. The minimum value.\n     * • max: A number Provider. The maximum value.\n     * OR\n     * • value: Shorthand version of value above, used to check for a single value only. Number providers cannot be used in this shorthand form.\n     * • period: If present, the day time is first reduced modulo the given number before being checked against value. For example, setting this to 24000 causes the checked time to be equal to the current daytime.\n     */\n    record TimeCheck(Value value, @Nullable Integer period) implements Predicate {\n        @Override\n        public String condition() {\n            return \"time_check\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n\n        public sealed interface Value {\n\n            static Value fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMapMapped(reader, Map.of(\n                        JsonReader.Token.NUMBER, DatapackLoader.moshi(Single.class),\n                        JsonReader.Token.BEGIN_OBJECT, DatapackLoader.moshi(MinMax.class)\n                ));\n            }\n\n            record MinMax(NumberProvider min, NumberProvider max) implements Value {\n            }\n\n            record Single(int value) implements Value {\n            }\n        }\n    }\n\n    /**\n     * value_check—Compares a number against another number or range of numbers. Invokable from any context.\n     * • value: A number Provider. The number to test.\n     * • range: The range of numbers to compare value against.\n     * • min: A number Provider. The minimum value.\n     * • max: A number Provider. The maximum value.\n     * OR\n     * • range: Shorthand version of range above, used to compare value against a single number only. Number providers cannot be used in this shorthand form.\n     */\n    record ValueCheck(NumberProvider value, Range range) implements Predicate {\n        @Override\n        public String condition() {\n            return \"value_check\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n\n        public sealed interface Range {\n            static Range fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.typeMapMapped(reader, Map.of(\n                        JsonReader.Token.NUMBER, DatapackLoader.moshi(Single.class),\n                        JsonReader.Token.BEGIN_OBJECT, DatapackLoader.moshi(MinMax.class)\n                ));\n            }\n\n            record MinMax(NumberProvider min, NumberProvider max) implements Range {\n            }\n\n            record Single(int value) implements Range {\n            }\n        }\n    }\n\n    /**\n     * weather_check—Checks the current game weather. Invokable from any context.\n     * • raining: If true, the condition passes only if it is raining or thundering.\n     * • thundering: If true, the condition passes only if it is thundering.\n     */\n    record WeatherCheck(boolean raining, boolean thundering) implements Predicate {\n        @Override\n        public String condition() {\n            return \"weather_check\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            // TODO: Implement conditions\n            return false;\n        }\n    }\n\n    /**\n     * all_of- Evaluates a list of predicates and passes if all of them pass. Invokable from any context.\n     * • terms: The list of predicates to evaluate. A predicate within this array must be an object.\n     */\n    record AllOf(List<Predicate> terms) implements Predicate {\n        @Override\n        public String condition() {\n            return \"all_of\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            return terms.stream().allMatch(predicate -> predicate.test(context));\n        }\n    }\n\n    /**\n     * any_of—Evaluates a list of predicates and passes if any of them pass. Invokable from any context.\n     * • terms: The list of predicates to evaluate. A predicate within this array must be an object.\n     */\n    record AnyOf(List<Predicate> terms) implements Predicate {\n        @Override\n        public String condition() {\n            return \"any_of\";\n        }\n\n        @Override\n        public boolean test(LootContext context) {\n            return terms.stream().anyMatch(predicate -> predicate.test(context));\n        }\n    }\n\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/function/LootFunction.java",
    "content": "package net.minestom.vanilla.datapack.loot.function;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.random.RandomGenerator;\n\n// aka ItemFunction, or ItemModifier\n// https://minecraft.fandom.com/wiki/Item_modifier\npublic interface LootFunction extends InBuiltLootFunctions {\n\n    /**\n     * @return The function id.\n     */\n    Key function();\n\n    /**\n     * Applies the function to the item stack.\n     *\n     * @param context the function context\n     * @return the modified item stack\n     */\n    ItemStack apply(Context context);\n\n    static LootFunction fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.unionStringTypeMapAdapted(reader, \"function\", Map.ofEntries(\n                Map.entry(\"minecraft:apply_bonus\", ApplyBonus.class),\n                Map.entry(\"minecraft:copy_name\", CopyName.class),\n                Map.entry(\"minecraft:copy_nbt\", CopyNbt.class),\n                Map.entry(\"minecraft:copy_state\", CopyState.class),\n                Map.entry(\"minecraft:enchant_randomly\", EnchantRandomly.class),\n                Map.entry(\"minecraft:enchant_with_levels\", EnchantWithLevels.class),\n                Map.entry(\"minecraft:exploration_map\", ExplorationMap.class),\n                Map.entry(\"minecraft:explosion_decay\", ExplosionDecay.class),\n                Map.entry(\"minecraft:fill_player_head\", FillPlayerHead.class),\n                Map.entry(\"minecraft:furnace_smelt\", FurnaceSmelt.class),\n                Map.entry(\"minecraft:limit_count\", LimitCount.class),\n                Map.entry(\"minecraft:looting_enchant\", LootingEnchant.class),\n                Map.entry(\"minecraft:set_attributes\", SetAttributes.class),\n                Map.entry(\"minecraft:set_banner_pattern\", SetBannerPattern.class),\n                Map.entry(\"minecraft:set_contents\", SetContents.class),\n                Map.entry(\"minecraft:set_count\", SetCount.class),\n                Map.entry(\"minecraft:set_damage\", SetDamage.class),\n                Map.entry(\"minecraft:set_enchantments\", SetEnchantments.class),\n                Map.entry(\"minecraft:set_instrument\", SetInstrument.class),\n                Map.entry(\"minecraft:set_loot_table\", SetLootTable.class),\n                Map.entry(\"minecraft:set_lore\", SetLore.class),\n                Map.entry(\"minecraft:set_name\", SetName.class),\n                Map.entry(\"minecraft:set_nbt\", SetNBT.class),\n                Map.entry(\"minecraft:set_potion\", SetPotion.class),\n                Map.entry(\"minecraft:set_stew_effect\", SetStewEffect.class)\n        ));\n    }\n\n    /**\n     * The context of the function.\n     */\n    interface Context extends LootContext {\n        /**\n         * The random generator used by the function.\n         *\n         * @return the random generator\n         */\n        RandomGenerator random();\n\n        /**\n         * The item stack to apply the function to.\n         *\n         * @return the previous item stack\n         */\n        ItemStack itemStack();\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/loot/function/Predicate.java",
    "content": "package net.minestom.vanilla.datapack.loot.function;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\n\nimport java.io.IOException;\n\npublic interface Predicate extends InBuiltPredicates {\n\n    String condition();\n\n    boolean test(LootContext context);\n\n    static Predicate fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.unionStringTypeAdapted(reader, \"condition\", condition -> switch (condition) {\n            case \"minecraft:alternative\" -> Alternative.class;\n            case \"minecraft:block_state_property\" -> BlockStateProperty.class;\n            case \"minecraft:damage_source_properties\" -> DamageSourceProperties.class;\n            case \"minecraft:entity_properties\" -> EntityProperties.class;\n            case \"minecraft:entity_scores\" -> EntityScores.class;\n            case \"minecraft:inverted\" -> Inverted.class;\n            case \"minecraft:killed_by_player\" -> KilledByPlayer.class;\n            case \"minecraft:location_check\" -> LocationCheck.class;\n            case \"minecraft:match_tool\" -> MatchTool.class;\n            case \"minecraft:random_chance\" -> RandomChance.class;\n            case \"minecraft:random_chance_with_looting\" -> RandomChanceWithLooting.class;\n            case \"minecraft:reference\" -> Reference.class;\n            case \"minecraft:survives_explosion\" -> SurvivesExplosion.class;\n            case \"minecraft:table_bonus\" -> TableBonus.class;\n            case \"minecraft:time_check\" -> TimeCheck.class;\n            case \"minecraft:value_check\" -> ValueCheck.class;\n            case \"minecraft:weather_check\" -> WeatherCheck.class;\n            case \"minecraft:any_of\" -> AnyOf.class;\n            case \"minecraft:all_of\" -> AllOf.class;\n            default -> null;\n        });\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/nbt/NBTUtils.java",
    "content": "package net.minestom.vanilla.datapack.nbt;\n\nimport net.kyori.adventure.nbt.BinaryTag;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.ListBinaryTag;\nimport net.kyori.adventure.nbt.TagStringIO;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.StringReader;\n\n/**\n * Contains various NBT-related utilities\n */\npublic class NBTUtils {\n\n    /**\n     * Checks to see if everything in {@code guarantee} is contained in {@code comparison}. The comparison is allowed to\n     * have extra fields that are not contained in the guarantee.\n     * @param guarantee the guarantee that the comparison must have all elements of\n     * @param comparison the comparison, that is being compared against the guarantee. NBT compounds in this parameter,\n     *                   whether deeper in the tree or not, are allowed to have keys that the guarantee does not - it's\n     *                   basically compared against a standard.\n     * @param assureListOrder whether to assure list order. When true, lists are directly compared, but when\n     *                        false, the comparison is checked to see if it contains each item in the guarantee.\n     * @return true if the comparison fits the guarantee, otherwise false\n     */\n    public static boolean compareNBT(@Nullable BinaryTag guarantee, @Nullable BinaryTag comparison, boolean assureListOrder) {\n        if (guarantee == null) {\n            // If there's no guarantee, it must always pass\n            return true;\n        }\n        if (comparison == null) {\n            // If it's null at this point, we already assured that guarantee is not null, so it must be invalid\n            return false;\n        }\n        if (!guarantee.type().equals(comparison.type())) {\n            // If the types aren't equal it can't fulfill the guarantee anyway\n            return false;\n        }\n        // If the list order is assured, it will be handled with the simple #equals call later in the method\n        if (!assureListOrder && guarantee instanceof ListBinaryTag guaranteeList) {\n            ListBinaryTag comparisonList = ((ListBinaryTag) comparison);\n            if (guaranteeList.size() == 0) {\n                return comparisonList.size() == 0;\n            }\n            for (BinaryTag nbt : guaranteeList) {\n                boolean contains = false;\n                for (BinaryTag compare : comparisonList) {\n                    if (compareNBT(nbt, compare, false)) {\n                        contains = true;\n                        break;\n                    }\n                }\n                if (!contains) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        if (guarantee instanceof CompoundBinaryTag guaranteeCompound) {\n            CompoundBinaryTag comparisonCompound = ((CompoundBinaryTag) comparison);\n            for (String key : guaranteeCompound.keySet()) {\n                if (!compareNBT(guaranteeCompound.get(key), comparisonCompound.get(key), assureListOrder)) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        return guarantee.equals(comparison);\n    }\n\n    /**\n     * Reads a NBT compound from the provided reader.<br>\n     * This implementation may be slow, as it may try to parse NBT many times, but this is unavoidable for now.\n     */\n    public static @Nullable CompoundBinaryTag readCompoundSNBT(@NotNull StringReader reader) throws IOException {\n        if (reader.read() != '{') {\n            return null;\n        }\n        StringBuilder string = new StringBuilder(\"{\");\n\n        while (true) {\n            // Since this is a compound we should always read to at least the next closing curly brackets. However, we\n            // can't count brackets and skip to until we think they should be valid because they could be escaped.\n\n            int next;\n            do {\n                next = reader.read();\n\n                if (next == -1) {\n                    return null;\n                }\n\n                string.appendCodePoint(next);\n            } while (next != '}');\n\n            try {\n                return TagStringIO.get().asCompound(string.toString());\n            } catch (IOException ignored) {}\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/number/DoubleNumberProviders.java",
    "content": "package net.minestom.vanilla.datapack.number;\n\nimport java.util.random.RandomGenerator;\n\ninterface DoubleNumberProviders {\n\n    record Constant(double value) implements NumberProvider.Double {\n        @Override\n        public double apply(NumberProvider.Context context) {\n            return value;\n        }\n    }\n\n    record Uniform(NumberProvider.Double min, NumberProvider.Double max) implements NumberProvider.Double {\n        @Override\n        public double apply(NumberProvider.Context context) {\n            double min = this.min.apply(context);\n            double max = this.max.apply(context);\n            return context.random().nextDouble(min, max);\n        }\n    }\n\n    record Binomial(NumberProvider.Int n, NumberProvider.Double p) implements NumberProvider.Double {\n        @Override\n        public double apply(NumberProvider.Context context) {\n            int n = this.n.apply(context);\n            double p = this.p.apply(context);\n            RandomGenerator random = context.random();\n            double sum = 0;\n            for (int i = 0; i < n; i++) {\n                if (random.nextDouble() < p) {\n                    sum++;\n                }\n            }\n            return sum;\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/number/IntNumberProviders.java",
    "content": "package net.minestom.vanilla.datapack.number;\n\nimport java.util.random.RandomGenerator;\n\ninterface IntNumberProviders {\n\n    record Constant(int value) implements NumberProvider.Int {\n        @Override\n        public int apply(NumberProvider.Context context) {\n            return value;\n        }\n    }\n\n    record Uniform(NumberProvider.Int min, NumberProvider.Int max) implements NumberProvider.Int {\n        @Override\n        public int apply(NumberProvider.Context context) {\n            int min = this.min.apply(context);\n            int max = this.max.apply(context);\n            return context.random().nextInt(min, max);\n        }\n    }\n\n    record Binomial(NumberProvider.Int n, NumberProvider.Double p) implements NumberProvider.Int {\n        @Override\n        public int apply(NumberProvider.Context context) {\n            int n = this.n.apply(context);\n            double p = this.p.apply(context);\n            RandomGenerator random = context.random();\n            double sum = 0;\n            for (int i = 0; i < n; i++) {\n                if (random.nextDouble() < p) {\n                    sum++;\n                }\n            }\n            return (int) sum;\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/number/NumberProvider.java",
    "content": "package net.minestom.vanilla.datapack.number;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.random.RandomGenerator;\n\npublic interface NumberProvider {\n\n    Int asInt();\n\n    Double asDouble();\n\n    interface Context {\n        // TODO: Scoreboard query\n        RandomGenerator random();\n    }\n\n    interface Int extends NumberProvider, IntNumberProviders {\n\n        int apply(Context context);\n\n        default Double asDouble() {\n            return context -> (double) apply(context);\n        }\n\n        default Int asInt() {\n            return this;\n        }\n\n        static NumberProvider.Int fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.typeMapMapped(reader, Map.of(\n                    JsonReader.Token.NUMBER, json -> constant(reader.nextInt()),\n                    JsonReader.Token.BEGIN_OBJECT, json -> JsonUtils.unionStringTypeMapAdapted(json, \"type\", Map.of(\n                            \"minecraft:constant\", Constant.class,\n                            \"minecraft:uniform\", Uniform.class,\n                            \"minecraft:binomial\", Binomial.class\n                    ))\n            ));\n        }\n\n        static NumberProvider.Int constant(int value) {\n            return new IntNumberProviders.Constant(value);\n        }\n\n        static NumberProvider.Int uniform(NumberProvider.Int min, NumberProvider.Int max) {\n            return new IntNumberProviders.Uniform(min, max);\n        }\n\n        static NumberProvider.Int binomial(NumberProvider.Int n, NumberProvider.Double p) {\n            return new IntNumberProviders.Binomial(n, p);\n        }\n    }\n\n    interface Double extends NumberProvider, DoubleNumberProviders {\n\n        double apply(Context context);\n\n        default Int asInt() {\n            return context -> (int) apply(context);\n        }\n\n        default Double asDouble() {\n            return this;\n        }\n\n        static NumberProvider.Double fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.typeMapMapped(reader, Map.of(\n                    JsonReader.Token.NUMBER, json -> constant(reader.nextDouble()),\n                    JsonReader.Token.BEGIN_OBJECT, json -> JsonUtils.unionStringTypeMapAdapted(json, \"type\", Map.of(\n                            \"minecraft:constant\", Constant.class,\n                            \"minecraft:uniform\", Uniform.class,\n                            \"minecraft:binomial\", Binomial.class\n                    ))\n            ));\n        }\n\n        static NumberProvider.Double constant(double value) {\n            return new DoubleNumberProviders.Constant(value);\n        }\n\n        static NumberProvider.Double uniform(NumberProvider.Double min, NumberProvider.Double max) {\n            return new DoubleNumberProviders.Uniform(min, max);\n        }\n\n        static NumberProvider.Double binomial(NumberProvider.Int n, NumberProvider.Double p) {\n            return new DoubleNumberProviders.Binomial(n, p);\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/recipe/Recipe.java",
    "content": "package net.minestom.vanilla.datapack.recipe;\n\nimport com.squareup.moshi.Json;\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\npublic interface Recipe {\n\n    @NotNull Key type();\n\n    @Nullable String group();\n\n    static Recipe fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch(type) {\n            case \"minecraft:blasting\" -> Blasting.class;\n            case \"minecraft:campfire_cooking\" -> CampfireCooking.class;\n            case \"minecraft:crafting_shaped\" -> Shaped.class;\n            case \"minecraft:crafting_shapeless\" -> Shapeless.class;\n            case \"minecraft:crafting_transmute\" -> Transmute.class;\n            case \"minecraft:crafting_special_armordye\" -> Special.ArmorDye.class;\n            case \"minecraft:crafting_special_bannerduplicate\" -> Special.BannerDuplicate.class;\n            case \"minecraft:crafting_special_bookcloning\" -> Special.BookCloning.class;\n            case \"minecraft:crafting_special_firework_rocket\" -> Special.FireworkRocket.class;\n            case \"minecraft:crafting_special_firework_star\" -> Special.FireworkStar.class;\n            case \"minecraft:crafting_special_firework_star_fade\" -> Special.FireworkStarFade.class;\n            case \"minecraft:crafting_special_mapcloning\" -> Special.MapCloning.class;\n            case \"minecraft:crafting_special_mapextending\" -> Special.MapExtending.class;\n            case \"minecraft:crafting_special_repairitem\" -> Special.RepairItem.class;\n            case \"minecraft:crafting_special_shielddecoration\" -> Special.ShieldDecoration.class;\n            case \"minecraft:crafting_special_tippedarrow\" -> Special.TippedArrow.class;\n            case \"minecraft:crafting_special_suspiciousstew\" -> Special.SuspiciousStew.class;\n            case \"minecraft:crafting_decorated_pot\" -> DecoratedPot.class;\n            case \"minecraft:smelting\" -> Smelting.class;\n            case \"minecraft:smithing\" -> Smithing.class;\n            case \"minecraft:smoking\" -> Smoking.class;\n            case \"minecraft:stonecutting\" -> Stonecutting.class;\n            case \"minecraft:smithing_trim\" -> SmithingTrim.class;\n            case \"minecraft:smithing_transform\" -> SmithingTransform.class;\n            default -> null;\n        });\n    }\n\n    interface CookingRecipe extends Recipe {\n        @NotNull List<Ingredient> ingredient();\n        @NotNull SingleResult result();\n        double experience();\n        @Optional Integer cookingTime();\n    }\n\n    interface Ingredient {\n\n        static Ingredient fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.<Ingredient>typeMapMapped(reader, Map.of(\n                    JsonReader.Token.BEGIN_ARRAY, json -> {\n                        Stream.Builder<Single> items = Stream.builder();\n                        json.beginArray();\n                        while (json.peek() != JsonReader.Token.END_ARRAY) {\n                            items.add(DatapackLoader.moshi(Single.class).apply(json));\n                        }\n                        json.endArray();\n                        return new Multi(items.build().toList());\n                    },\n                    JsonReader.Token.STRING, DatapackLoader.moshi(Single.class),\n                    JsonReader.Token.NULL, json -> {\n                        json.nextNull();\n                        return new None();\n                    }\n            ));\n        }\n\n        // single means within an array, not necessarily a singular item\n        interface Single extends Ingredient {\n            static Single fromJson(JsonReader reader) throws IOException {\n                String content = reader.nextString();\n                boolean isTag = content.startsWith(\"#\");\n                if (isTag) {\n                    return new Tag(Key.key(content.substring(1)));\n                }\n                return new Item(Material.fromKey(content));\n            }\n        }\n\n        record Item(Material item) implements Single {\n        }\n\n        record Tag(Key tag) implements Single {\n        }\n\n        record None() implements Ingredient {\n        }\n\n        record Multi(List<Single> items) implements Ingredient {\n        }\n    }\n\n    record Result(Material id, @Optional Integer count) {\n    }\n\n    record SingleResult(Material id) {\n    }\n\n    record Blasting(String group, @Optional String category, JsonUtils.SingleOrList<Ingredient> ingredient, SingleResult result,\n                    double experience, @Optional @Json(name = \"cookingtime\") Integer cookingTime) implements CookingRecipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:blasting\");\n        }\n    }\n\n    record CampfireCooking(String group, JsonUtils.SingleOrList<Ingredient> ingredient, SingleResult result,\n                           double experience, @Optional @Json(name = \"cookingtime\") Integer cookingTime) implements CookingRecipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:campfire_cooking\");\n        }\n    }\n\n    record Shaped(String group, @Optional String category, List<String> pattern, Map<Character, Ingredient> key, Result result) implements Recipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:crafting_shaped\");\n        }\n    }\n\n    record Shapeless(String group, @Optional String category, JsonUtils.SingleOrList<Ingredient> ingredients, Result result) implements Recipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:crafting_shapeless\");\n        }\n    }\n\n    record Transmute(String group, @Optional String category, JsonUtils.SingleOrList<Ingredient> input,\n                     JsonUtils.SingleOrList<Ingredient> material, Result result) implements Recipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:crafting_transmute\");\n        }\n    }\n\n    sealed interface Special extends Recipe {\n\n        record ArmorDye(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_armordye\");\n            }\n        }\n\n        record BannerDuplicate(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_bannerduplicate\");\n            }\n        }\n\n        record BookCloning(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_bookcloning\");\n            }\n        }\n\n        record FireworkRocket(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_firework_rocket\");\n            }\n        }\n\n        record FireworkStar(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_firework_star\");\n            }\n        }\n\n        record FireworkStarFade(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_firework_star_fade\");\n            }\n        }\n\n        record MapCloning(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_mapcloning\");\n            }\n        }\n\n        record MapExtending(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_mapextending\");\n            }\n        }\n\n        record RepairItem(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_repairitem\");\n            }\n        }\n\n        record ShieldDecoration(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_shielddecoration\");\n            }\n        }\n\n        record TippedArrow(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_tippedarrow\");\n            }\n        }\n\n        record SuspiciousStew(String group) implements Special {\n            @Override\n            public @NotNull Key type() {\n                return Key.key(\"minecraft:crafting_special_suspiciousstew\");\n            }\n        }\n    }\n\n\n    record DecoratedPot(String group, String category) implements Recipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:decorated_pot\");\n        }\n    }\n\n    record Smelting(String group, @Optional String category, JsonUtils.SingleOrList<Ingredient> ingredient, SingleResult result,\n                    double experience, @Optional @Json(name = \"cookingtime\") Integer cookingTime) implements CookingRecipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:smelting\");\n        }\n    }\n\n    record Smoking(String group, JsonUtils.SingleOrList<Ingredient> ingredient, SingleResult result,\n                   double experience, @Optional @Json(name = \"cookingtime\") Integer cookingTime) implements CookingRecipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:smoking\");\n        }\n    }\n\n    record Stonecutting(@Nullable String group, JsonUtils.SingleOrList<Ingredient> ingredient, Result result) implements Recipe {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:stonecutting\");\n        }\n    }\n\n    interface Smithing extends Recipe {\n\n        Ingredient.Single template();\n        Ingredient.Single base();\n        Ingredient.Single addition();\n        default @NotNull Key type() {\n            return Key.key(\"minecraft:smithing\");\n        }\n    }\n\n    record SmithingTrim(String group, Ingredient.Single base, Ingredient.Single addition, String pattern,\n                        Ingredient.Single template) implements Smithing {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:smithing_trim\");\n        }\n    }\n\n    record SmithingTransform(String group, Ingredient.Single base, Ingredient.Single addition, Result result,\n                             Ingredient.Single template) implements Smithing {\n        @Override\n        public @NotNull Key type() {\n            return Key.key(\"minecraft:smithing_transform\");\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/tags/ConditionsFor.java",
    "content": "package net.minestom.vanilla.datapack.tags;\n\npublic class ConditionsFor {\n    public record Location() {\n    }\n\n    public record Item() {\n    }\n\n    public record Entity() {\n\n    }\n\n    public record Damage() {\n    }\n\n    public record DamageTypes() {\n    }\n\n    public record Distance() {\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/tags/Tag.java",
    "content": "package net.minestom.vanilla.datapack.tags;\n\nimport net.kyori.adventure.key.Key;\nimport org.jetbrains.annotations.NotNull;\n\npublic record Tag(String namespace, String value) implements Key {\n    public Tag(String string) {\n        this(string.split(\":\")[0], string.split(\":\")[1]);\n    }\n\n    @Override\n    public @NotNull String asString() {\n        return namespace + \":\" + value;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/trims/TrimMaterial.java",
    "content": "package net.minestom.vanilla.datapack.trims;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.text.Component;\nimport net.minestom.vanilla.datapack.json.Optional;\n\nimport java.util.Map;\n\n/**\n *\n The root object\n asset_name: A string which will be used in the resource pack.\n description: A JSON text component used for the tooltip on items. The color\n #258474 is used here.\n override_armor_materials: Optional. Map of armor material to override color palette.\n */\npublic record TrimMaterial(String asset_name, Component description, @Optional Map<Key, String> override_armor_materials) {\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/trims/TrimPattern.java",
    "content": "package net.minestom.vanilla.datapack.trims;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.text.Component;\nimport net.minestom.vanilla.datapack.json.Optional;\n\n/**\n *\n The root object\n asset_id: A resource location which will be used in the resource pack.\n description: A JSON text component used for the tooltip on items.\n template_item: The item representing this pattern.\n decal: Optional, defaults to false. If true, the pattern texture will be masked based on the underlying armor.\n */\npublic record TrimPattern(Key asset_id, Component description, Key template_item, @Optional Boolean decal) {\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/Biome.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\n\n/**\n * Represents a biome in the game world.\n *\n * @param has_precipitation Determines whether the biome has precipitation or not.\n * @param temperature Controls gameplay features like grass and foliage color, and a height adjusted temperature.\n * @param temperature_modifier (optional, defaults to none) Modifies temperature before calculating the height adjusted temperature.\n * @param downfall Controls grass and foliage color.\n * @param effects Ambient effects in this biome.\n * @param carvers The carvers to use. TODO: Carvers\n * @param features List of generation steps (Can be empty). Usually, there are 11 steps, but any amount is possible. TODO: Features\n * @param creature_spawn_probability (optional) Higher value results in more creatures spawned in world generation.\n * @param spawners (Required, but can be empty. If this object doesn't contain a certain category, mobs in this category do not spawn.) Entity spawning settings.\n * @param spawn_costs (Required, but can be empty. Only mobs listed here use the spawn cost mechanism) See Spawn#Spawn costs for details.\n */\npublic record Biome(\n        boolean has_precipitation,\n        float temperature,\n        @Optional TemperatureModifier temperature_modifier,\n        float downfall,\n        Effects effects,\n        CarversList carvers,\n        Object features,\n        @Optional Float creature_spawn_probability,\n        Map<MobCategory, List<SpawnerData>> spawners,\n        Map<Key, SpawnCost> spawn_costs\n) {\n\n    /**\n     * Enumeration of temperature modifiers.\n     */\n    public enum TemperatureModifier {\n        none,\n        frozen\n    }\n\n    /**\n     * Represents a sound in the game world.\n     */\n    public interface Sound {\n        Key type();\n\n        /**\n         * Reads a Sound from JSON.\n         *\n         * @param reader The JSON reader.\n         * @return The constructed Sound.\n         * @throws IOException If an IO error occurs.\n         */\n        static Sound fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.<Sound>typeMap(reader, token -> switch (token) {\n                case STRING -> json -> new SoundID(Key.key(json.nextString()));\n                case BEGIN_OBJECT -> json -> JsonUtils.unionStringTypeAdapted(json, \"type\", type -> switch (type) {\n                    case \"sound_id\" -> SoundID.class;\n                    case \"range\" -> Range.class;\n                    default -> null;\n                });\n                default -> null;\n            });\n        }\n\n        /**\n         * Represents a sound with a namespace ID.\n         */\n        record SoundID(Key value) implements Sound {\n            @Override\n            public Key type() {\n                return Key.key(\"sound_id\");\n            }\n        }\n\n        /**\n         * Represents a sound with a range.\n         */\n        record Range(@Optional Float value) implements Sound {\n            @Override\n            public Key type() {\n                return Key.key(\"range\");\n            }\n        }\n    }\n\n    /**\n     * Represents the effects of a biome.\n     */\n    public record Effects(\n            int fog_color,\n            int sky_color,\n            int water_color,\n            int water_fog_color,\n            @Optional Integer foliage_color,\n            @Optional Integer grass_color,\n            @Optional GrassColorModifier grass_color_modifier,\n            @Optional Particle particle,\n            @Optional Sound ambient_sound,\n            @Optional MoodSound mood_sound,\n            @Optional AdditionsSound additions_sound,\n            @Optional List<Music> music\n    ) {\n\n        /**\n         * Represents a particle effect in the game world.\n         */\n        public record Particle(float probability, Options options) {\n\n            /**\n             * Represents options for particle effects.\n             */\n            public interface Options {\n                /**\n                 * Gets the namespaced ID of the particle type.\n                 *\n                 * @return The namespaced ID of the particle type.\n                 */\n                Key type();\n\n                /**\n                 * Reads options from JSON.\n                 *\n                 * @param reader The JSON reader.\n                 * @return The constructed Options.\n                 * @throws IOException If an IO error occurs.\n                 */\n                static Options fromJson(JsonReader reader) throws IOException {\n                    return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch (type) {\n                        case \"minecraft:block\" -> Block.class;\n                        case \"minecraft:block_marker\" -> BlockMarker.class;\n                        case \"minecraft:falling_dust\" -> FallingDust.class;\n                        case \"minecraft:item\" -> Item.class;\n                        case \"minecraft:dust\" -> Dust.class;\n                        case \"minecraft:dust_color_transition\" -> DustColorTransition.class;\n                        case \"minecraft:sculk_charge\" -> SculkCharge.class;\n                        case \"minecraft:vibration\" -> Vibration.class;\n                        case \"minecraft:shriek\" -> Shriek.class;\n                        default -> Generic.class;\n                    });\n                }\n\n                /**\n                 * Represents a block particle.\n                 */\n                record Block(BlockState value) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"block\");\n                    }\n                }\n\n                /**\n                 * Represents a block marker particle.\n                 */\n                record BlockMarker(BlockState value) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"block_marker\");\n                    }\n                }\n\n                /**\n                 * Represents a falling dust particle.\n                 */\n                record FallingDust(BlockState value) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"falling_dust\");\n                    }\n                }\n\n                /**\n                 * Represents an item particle.\n                 */\n                record Item(Value value) implements Options {\n\n                    /**\n                     * Represents the value of an item particle.\n                     */\n                    record Value(Key id, int count, CompoundBinaryTag tag) {\n\n                    }\n\n                    @Override\n                    public Key type() {\n                        return Key.key(\"item\");\n                    }\n                }\n\n                /**\n                 * Represents a dust particle.\n                 */\n                record Dust(List<Float> color, float scale) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"dust\");\n                    }\n                }\n\n                /**\n                 * Represents a dust color transition particle.\n                 */\n                record DustColorTransition(List<Float> fromColor, List<Float> toColor, float scale) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"dust_color_transition\");\n                    }\n                }\n\n                /**\n                 * Represents a sculk charge particle.\n                 */\n                record SculkCharge(float roll) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"sculk_charge\");\n                    }\n                }\n\n                /**\n                 * Represents a vibration particle.\n                 */\n                record Vibration(PositionSource destination, int arrival_in_ticks) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"vibration\");\n                    }\n\n                    /**\n                     * Represents a position source for the vibration particle.\n                     */\n                    interface PositionSource {\n                        Key type();\n\n                        /**\n                         * Reads a PositionSource from JSON.\n                         *\n                         * @param reader The JSON reader.\n                         * @return The constructed PositionSource.\n                         * @throws IOException If an IO error occurs.\n                         */\n                        static PositionSource fromJson(JsonReader reader) throws IOException {\n                            return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch (type) {\n                                case \"minecraft:block\" -> Block.class;\n                                case \"minecraft:entity\" -> Entity.class;\n                                default -> null;\n                            });\n                        }\n\n                        /**\n                         * Represents a block position for the vibration particle.\n                         */\n                        record Block(int x, int y, int z) implements PositionSource {\n                            @Override\n                            public Key type() {\n                                return Key.key(\"block\");\n                            }\n                        }\n\n                        /**\n                         * Represents an entity position source for the vibration particle.\n                         */\n                        record Entity(UUID source_entity, @Optional Float y_offset) implements PositionSource {\n                            @Override\n                            public Key type() {\n                                return Key.key(\"entity\");\n                            }\n                        }\n                    }\n                }\n\n                /**\n                 * Represents a shriek particle.\n                 */\n                record Shriek(int delay) implements Options {\n                    @Override\n                    public Key type() {\n                        return Key.key(\"shriek\");\n                    }\n                }\n\n                /**\n                 * Represents generic particle options.\n                 */\n                record Generic(Key type) implements Options {\n                }\n            }\n        }\n\n        /**\n         * Represents mood sound settings for a biome.\n         */\n        public record MoodSound(Sound sound, int tick_delay, int block_search_extent, double offset) {\n        }\n\n        /**\n         * Represents additions sound settings for a biome.\n         */\n        public record AdditionsSound(Sound sound, double tick_chance) {\n        }\n\n        /**\n         * Represents music settings for a biome.\n         */\n        public record Music(MusicData data, double weight) {\n        }\n\n        public record MusicData(Sound sound, int min_delay, int max_delay, boolean replace_current_music) {\n        }\n    }\n\n    public interface CarversList {\n        //  air: carver (referenced by ID or inlined), or carver #tag or list (containing either IDs or inlined objects) (Optional; can be empty) — Carvers for the air cave generation step.\n\n        List<Carver> carvers();\n\n        static CarversList fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.<CarversList>typeMap(reader, token -> switch (token) {\n                case STRING -> json -> new Single.Reference(Key.key(json.nextString()));\n                case BEGIN_OBJECT, BEGIN_ARRAY -> json -> {\n                    var singleOrList = JsonUtils.SingleOrList.<CarversList.Single>fromJson(CarversList.Single.class, json);\n                    if (!singleOrList.isList()) {\n                        return new Single.Inlined(singleOrList.asObject().carver());\n                    }\n                    return new Multiple(singleOrList.asList());\n                };\n                default -> null;\n            });\n        }\n\n        interface Single extends CarversList {\n            Carver carver();\n\n            default List<Carver> carvers() {\n                return List.of(carver());\n            }\n\n            static Single fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.<Single>typeMap(reader, token -> switch (token) {\n                    case STRING -> json -> new Reference(Key.key(json.nextString()));\n                    case BEGIN_OBJECT -> json -> new Inlined(DatapackLoader.moshi(Carver.class).apply(json));\n                    default -> null;\n                });\n            }\n\n            final class Reference implements Single {\n                private final Key id;\n                private @Nullable Carver carver = null;\n\n                public Reference(Key id) {\n                    this.id = id;\n                    DatapackLoader.loading().whenFinished(finisher -> {\n                        for (var entry : finisher.datapack().namespacedData().entrySet()) {\n                            String namespace = entry.getKey();\n\n                            var carvers = entry.getValue().world_gen().configured_carver();\n                            for (String file : carvers.files()) {\n                                var carver = carvers.file(file);\n\n                                Key carverId = Key.key(namespace, file.substring(0, file.length() - \".json\".length()));\n                                if (carverId.equals(id)) {\n                                    Reference.this.carver = carver;\n                                    return;\n                                }\n                            }\n                        }\n                    });\n                }\n\n                @Override\n                public Carver carver() {\n                    if (carver == null) {\n                        if (DatapackLoader.loading().isStatic()) {\n                            throw new IllegalStateException(\"Cannot load carver in a static context\");\n                        }\n                        throw new IllegalStateException(\"Carver not loaded yet\");\n                    }\n                    return carver;\n                }\n\n                public Key id() {\n                    return id;\n                }\n\n                @Override\n                public boolean equals(Object obj) {\n                    if (obj == this) return true;\n                    if (obj == null || obj.getClass() != this.getClass()) return false;\n                    var that = (Reference) obj;\n                    return Objects.equals(this.id, that.id);\n                }\n\n                @Override\n                public int hashCode() {\n                    return Objects.hash(id);\n                }\n\n                @Override\n                public String toString() {\n                    return \"Reference[\" +\n                            \"id=\" + id + ']';\n                }\n            }\n\n            record Inlined(Carver carver) implements Single {\n            }\n        }\n\n        record Multiple(List<CarversList.Single> singles) implements CarversList {\n            @Override\n            public List<Carver> carvers() {\n                return singles.stream().map(CarversList.Single::carver).toList();\n            }\n        }\n    }\n\n    /**\n     * The spawner data for a single mob.\n     * @param type The namespaced entity id of the mob.\n     * @param weight How often this mob should spawn, higher values produce more spawns.\n     * @param minCount The minimum count of mobs to spawn in a pack. Must be greater than 0.\n     * @param maxCount The maximum count of mobs to spawn in a pack. Must be greater than 0. And must be not less than  minCount.\n     */\n    public record SpawnerData(Key type, int weight, int minCount, int maxCount) {\n    }\n\n    public enum MobCategory {\n        monster,\n        creature,\n        ambient,\n        water_creature,\n        underground_water_creature,\n        water_ambient,\n        misc,\n        axolotls\n    }\n\n    /**\n     * Represents the spawn costs for a mob.\n     * @param energy_budget New mob's maximum potential.\n     * @param charge New mob's charge.\n     */\n    public record SpawnCost(double energy_budget, double charge) {\n    }\n\n    /**\n     * Enumeration of grass color modifiers.\n     */\n    public enum GrassColorModifier {\n        none,\n        dark_forest,\n        swamp\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/BlockState.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic record BlockState(String name, Map<String, String> properties) {\n    public Block toMinestom() {\n        return Objects.requireNonNull(Block.fromKey(name), () -> \"Unknown block: \" + name)\n                .withProperties(properties);\n    }\n\n    public static BlockState fromJson(JsonReader reader) throws IOException {\n        reader.beginObject();\n        String name = JsonUtils.findProperty(reader.peekJson(), \"Name\", JsonReader::nextString);\n        Map<String, String> properties = JsonUtils.findProperty(reader.peekJson(), \"Properties\", json -> JsonUtils.readObjectToMap(json, JsonReader::nextString));\n        Objects.requireNonNull(name, \"expected a non-null name while passing BlockState.\");\n        properties = Objects.requireNonNullElseGet(properties, Map::of);\n        while (reader.peek() != JsonReader.Token.END_OBJECT) {\n            reader.skipName();\n            reader.skipValue();\n        }\n        reader.endObject();\n        return new BlockState(name, properties);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/Carver.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\n\nimport java.io.IOException;\n\n//  The root object.\n//\n//     type: The ID of carver type.\n//     config: Configuration values for the carver.\npublic record Carver(Key type, BaseConfig config) {\n\n    public static Carver fromJson(JsonReader reader) throws IOException {\n        // carvers are a special case since the config object depends on the type of carver\n        String type;\n        try (var json = reader.peekJson()) {\n            json.beginObject();\n            type = JsonUtils.findProperty(json, \"type\", JsonReader::nextString);\n            if (type == null) {\n                throw new IOException(\"Missing type\");\n            }\n        }\n\n        var configReader = switch (type) {\n            case \"minecraft:cave\", \"minecraft:nether_cave\" -> DatapackLoader.moshi(CaveConfig.class);\n            case \"minecraft:canyon\" -> DatapackLoader.moshi(CanyonConfig.class);\n            default -> throw new IOException(\"Unknown carver type: \" + type);\n        };\n        reader.beginObject();\n        var config = JsonUtils.findProperty(reader, \"config\", configReader);\n        reader.endObject();\n        if (config == null) {\n            throw new IOException(\"Missing config\");\n        }\n        return new Carver(Key.key(type), config);\n    }\n\n    //     cave - Carves a cave. A cave is a long tunnel that sometimes branches. Somtimes one or more tunnels start from a circular void.\n    //    nether_cave - Similar to cave, but with a less frequency and wider tunnels. And aquifer doesn't work. The carved blocks below bottom_y + 32.0 are filled with lava.\n    //    canyon - Carves a canyon.\n\n    public interface BaseConfig {\n\n        //  probability: The probability that each chunk attempts to generate carvers. Value between 0 and 1 (both inclusive).\n        float probability();\n\n        //  y: The height at which this carver attempts to generate.\n        HeightProvider y();\n\n        //  lava_level: The Y-level below or equal to which the carved areas are filled with lava. Doesn't affect nether_cave (where lava level is always bottom_y + 31). (This field is seemingly ignored and always set to -56 (MC-237017), needs testing)\n        HeightProvider lava_level();\n\n        //  replaceable: Blocks that can be carved. Can be a block ID, a block tag, or a list of block IDs.\n        JsonUtils.SingleOrList<Block> replaceable();\n\n        // debug_settings: (optional) Replaces blocks in the carved areas for debugging.\n        //\n        //     debug_mode: (optional, defauts to false) Enable debug mode for this carver.\n        //     air_state: (optional, defaults to acacia button's default state) Replaces air blocks.\n        //     water_state: (optional, defaults to acacia button's default state) Replaces water blocks and then waterlogs these blocks.\n        //     lava_state: (optional, defaults to acacia button's default state) Replaces lava blocks.\n        //     barrier_state: (optional, defaults to acacia button's default state) Replaces barriers of aquifers.\n        @Optional\n        BaseConfig.DebugSettings debug_settings();\n\n        record DebugSettings(@Optional Boolean debug_mode, @Optional Block air_state,\n                                    @Optional Block water_state, @Optional Block lava_state,\n                                    @Optional Block barrier_state) {\n        }\n    }\n\n    public record Config(float probability, HeightProvider y, HeightProvider lava_level,\n                   JsonUtils.SingleOrList<Block> replaceable, @Optional BaseConfig.DebugSettings debug_settings) implements BaseConfig {\n    }\n\n    // If carver type is cave or nether_cave, additional fields are as follows:\n    //\n    // yScale: Vertically scales circular voids.\n    // vertical_radius_multiplier: Vertically scales cave tunnels. Doesn't affect the length of tunnels.\n    // floor_level: Value between -1.0 and 1.0 (both inclusive). Change the shape of the cave's horizontal floor. If 0.0, carves the terrain with ellipsoids. If 1.0, carves with upper semi-ellipsoids, resulting in a level floor.\n    public record CaveConfig(float probability, HeightProvider y, HeightProvider lava_level,\n                      JsonUtils.SingleOrList<Block> replaceable, @Optional BaseConfig.DebugSettings debug_settings,\n                      FloatProvider yScale, FloatProvider vertical_radius_multiplier,\n                      FloatProvider floor_level) implements BaseConfig {\n    }\n\n    // If carver type is canyon, additional fields are as follows:\n    //  yScale: Vertically scales canyons.\n    // vertical_rotation: Vertical rotation as a canyon extends.\n    //  shape: The shape to use for the ravine.\n    public record CanyonConfig(float probability, HeightProvider y, HeightProvider lava_level,\n                        JsonUtils.SingleOrList<Block> replaceable, @Optional BaseConfig.DebugSettings debug_settings,\n                        FloatProvider yScale, FloatProvider vertical_rotation, Shape shape) implements BaseConfig {\n        //  distance_factor: Scales the length of canyons. Higher values make canyons longer.\n        // thickness: Scales the breadth and height of canyons.\n        // horizontal_radius_factor: Scales the breadth of canyons. Higher values make canyons wider.\n        // vertical_radius_default_factor: Vertically scales canyons. Higher values make canyons deeper.\n        // vertical_radius_center_factor: Scales the height based on the horizontal distance from the canyon's center, resulting in deeper center.\n        // width_smoothness: Higher values smooth canyon walls on the vertical axis. Must be greater than 0.\n        public record Shape(\n                FloatProvider distance_factor,\n                FloatProvider thickness,\n                FloatProvider horizontal_radius_factor,\n                float vertical_radius_default_factor,\n                float vertical_radius_center_factor,\n                int width_smoothness) {\n        }\n    }\n\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/DensityFunction.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.worldgen.math.NumberFunction;\n\nimport java.io.IOException;\n\npublic interface DensityFunction extends DensityFunctions, NumberFunction<DensityFunction.Context> {\n    double compute(Context context);\n\n    default double minValue() {\n        return -maxValue();\n    }\n\n    double maxValue();\n\n    static DensityFunction fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMap(reader, token -> switch (token) {\n            case NUMBER -> json -> new Constant(json.nextDouble());\n            case STRING -> json -> new LazyLoadedDensityFunction(json.nextString(), DatapackLoader.loading());\n            case BEGIN_OBJECT -> json -> JsonUtils.unionStringTypeAdapted(json, \"type\", type -> switch (type) {\n                case \"minecraft:blend_alpha\" -> BlendAlpha.class;\n                case \"minecraft:blend_offset\" -> BlendOffset.class;\n                case \"minecraft:beardifier\" -> Beardifier.class;\n                case \"minecraft:old_blended_noise\" -> OldBlendedNoise.class;\n\n                case \"minecraft:flat_cache\" -> FlatCache.class;\n                case \"minecraft:interpolated\" -> Interpolated.class;\n                case \"minecraft:cache_2d\" -> Cache2D.class;\n                case \"minecraft:cache_once\" -> CacheOnce.class;\n                case \"minecraft:cache_all_in_cell\" -> CacheAllInCell.class;\n\n                case \"minecraft:noise\" -> NoiseRoot.class;\n                case \"minecraft:end_islands\" -> EndIslands.class;\n\n                case \"minecraft:weird_scaled_sampler\" -> WeirdScaledSampler.class;\n                case \"minecraft:shifted_noise\" -> ShiftedNoise.class;\n                case \"minecraft:range_choice\" -> RangeChoice.class;\n                case \"minecraft:shift_a\" -> ShiftA.class;\n                case \"minecraft:shift_b\" -> ShiftB.class;\n                case \"minecraft:shift\" -> Shift.class;\n                case \"minecraft:blend_density\" -> BlendDensity.class;\n\n                case \"minecraft:clamp\" -> Clamp.class;\n                case \"minecraft:abs\" -> Abs.class;\n                case \"minecraft:square\" -> Square.class;\n                case \"minecraft:cube\" -> Cube.class;\n                case \"minecraft:half_negative\" -> HalfNegative.class;\n                case \"minecraft:quarter_negative\" -> QuarterNegative.class;\n                case \"minecraft:squeeze\" -> Squeeze.class;\n                case \"minecraft:add\" -> Add.class;\n                case \"minecraft:mul\" -> Mul.class;\n                case \"minecraft:min\" -> Min.class;\n                case \"minecraft:max\" -> Max.class;\n\n                case \"minecraft:spline\" -> Spline.class;\n                case \"minecraft:constant\" -> Constant.class;\n                case \"minecraft:y_clamped_gradient\" -> YClampedGradient.class;\n                default -> null;\n            });\n            default -> null;\n        });\n    }\n\n    static DensityFunction.Context context(double x, double y, double z) {\n        ContextImpl context = new ContextImpl();\n        context.x = x;\n        context.y = y;\n        context.z = z;\n        return context;\n    }\n\n    interface Context {\n        double x();\n\n        default int blockX() {\n            return (int) Math.floor(x());\n        }\n\n        double y();\n\n        default int blockY() {\n            return (int) Math.floor(y());\n        }\n\n        double z();\n\n        default int blockZ() {\n            return (int) Math.floor(z());\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/DensityFunctions.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.Json;\nimport com.squareup.moshi.JsonReader;\nimport it.unimi.dsi.fastutil.doubles.Double2DoubleFunction;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.worldgen.math.CubicSpline;\nimport net.minestom.vanilla.datapack.worldgen.noise.BlendedNoise;\nimport net.minestom.vanilla.datapack.worldgen.noise.Noise;\nimport net.minestom.vanilla.datapack.worldgen.noise.SimplexNoise;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.storage.DoubleStorage;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.function.DoubleSupplier;\n\ninterface DensityFunctions {\n\n    class ContextImpl implements DensityFunction.Context {\n        public double x;\n        public double y;\n        public double z;\n\n        @Override\n        public double x() {\n            return x;\n        }\n\n        @Override\n        public double y() {\n            return y;\n        }\n\n        @Override\n        public double z() {\n            return z;\n        }\n    }\n\n    // blend_alpha goes from 0 (\"use old terrain\") to 1 (\"use new terrain\")\n    record BlendAlpha() implements DensityFunction {\n\n        @Override\n        public double compute(Context context) {\n            return 1;\n        }\n\n        @Override\n        public double maxValue() {\n            return 1;\n        }\n\n        @Override\n        public double minValue() {\n            return 0;\n        }\n    }\n\n    // blend_offset and blend_density are the offset and density values to use for the old terrain.\n    record BlendOffset() implements DensityFunction {\n\n        @Override\n        public double compute(Context context) {\n            return 0;\n        }\n\n        @Override\n        public double maxValue() {\n            return Double.POSITIVE_INFINITY;\n        }\n\n        @Override\n        public double minValue() {\n            return Double.NEGATIVE_INFINITY;\n        }\n    }\n\n    record Beardifier() implements DensityFunction {\n\n        @Override\n        public double compute(Context context) {\n            return 0;\n        }\n\n        @Override\n        public double maxValue() {\n            return Double.POSITIVE_INFINITY;\n        }\n\n        @Override\n        public double minValue() {\n            return Double.NEGATIVE_INFINITY;\n        }\n    }\n\n    class OldBlendedNoise implements DensityFunction {\n\n        private final BlendedNoise noise;\n\n        private OldBlendedNoise(Params params) {\n            this.noise = new BlendedNoise(DatapackLoader.loading().random(), params.xz_scale(), params.y_scale(), params.xz_factor(), params.y_factor(), params.smear_scale_multiplier());\n        }\n\n        public record Params(double xz_scale, double y_scale, double xz_factor, double y_factor, double smear_scale_multiplier) {\n        }\n\n        public static OldBlendedNoise fromJson(JsonReader reader) throws IOException {\n            Params params = DatapackLoader.moshi(Params.class).apply(reader);\n            return new OldBlendedNoise(params);\n        }\n\n        @Override\n        public double compute(Context context) {\n            return noise.sample(context.x(), context.y(), context.z());\n        }\n\n        @Override\n        public double maxValue() {\n            return noise.minValue();\n        }\n\n        @Override\n        public double minValue() {\n            return noise.maxValue();\n        }\n    }\n\n    interface Wrapped extends DensityFunction {\n        DensityFunction wrapped();\n\n        @Override\n        default double minValue() {\n            return wrapped().minValue();\n        }\n\n        @Override\n        default double maxValue() {\n            return wrapped().maxValue();\n        }\n    }\n\n    class FlatCache implements Wrapped {\n\n        private final DensityFunction argument;\n\n        private int lastQuartX = 0;\n        private int lastQuartZ = 0;\n        private double lastValue = 0;\n\n        public FlatCache(DensityFunction argument) {\n            this.argument = argument;\n        }\n\n        public double compute(Context context) {\n            int quartX = context.blockX() >> 2;\n            int quartZ = context.blockZ() >> 2;\n            if (this.lastQuartX != quartX || this.lastQuartZ != quartZ) {\n                this.lastValue = this.argument.compute(DensityFunction.context(quartX << 2, 0, quartZ << 2));\n                this.lastQuartX = quartX;\n                this.lastQuartZ = quartZ;\n            }\n            return this.lastValue;\n        }\n\n        @Override\n        public DensityFunction wrapped() {\n            return argument;\n        }\n    }\n\n    class Interpolated implements Wrapped {\n        private final DensityFunction argument;\n\n        @Json(ignore = true)\n        private @Nullable DoubleStorage cache;\n\n        public Interpolated(DensityFunction argument) {\n            this.argument = argument;\n        }\n\n        @Override\n        public DensityFunction wrapped() {\n            return argument();\n        }\n\n        private DoubleStorage cache() {\n            if (cache == null) {\n                cache = DoubleStorage.threadLocal(() -> DoubleStorage.from(argument).cache());\n            }\n            return cache;\n        }\n\n        @Override\n        public double compute(Context context) {\n            int blockX = context.blockX();\n            int blockY = context.blockY();\n            int blockZ = context.blockZ();\n            int w = 4;\n            int h = 4;\n            double x = ((blockX % w + w) % w) / (double) w;\n            double y = ((blockY % h + h) % h) / (double) h;\n            double z = ((blockZ % w + w) % w) / (double) w;\n            int firstX = Math.floorDiv(blockX, w) * w;\n            int firstY = Math.floorDiv(blockY, h) * h;\n            int firstZ = Math.floorDiv(blockZ, w) * w;\n            DoubleSupplier noise000 = () -> this.computeCorner(firstX, firstY, firstZ);\n            DoubleSupplier noise001 = () -> this.computeCorner(firstX, firstY, firstZ + w);\n            DoubleSupplier noise010 = () -> this.computeCorner(firstX, firstY + h, firstZ);\n            DoubleSupplier noise011 = () -> this.computeCorner(firstX, firstY + h, firstZ + w);\n            DoubleSupplier noise100 = () -> this.computeCorner(firstX + w, firstY, firstZ);\n            DoubleSupplier noise101 = () -> this.computeCorner(firstX + w, firstY, firstZ + w);\n            DoubleSupplier noise110 = () -> this.computeCorner(firstX + w, firstY + h, firstZ);\n            DoubleSupplier noise111 = () -> this.computeCorner(firstX + w, firstY + h, firstZ + w);\n            return Util.lazyLerp3(x, y, z, noise000, noise100, noise010, noise110, noise001, noise101, noise011, noise111);\n        }\n\n        private double computeCorner(int x, int y, int z) {\n            return cache().obtain(x, y, z);\n        }\n\n        public DensityFunction argument() {\n            return argument;\n        }\n    }\n\n    class Cache2D implements Wrapped {\n        // Only computes the input density once per horizonal position.\n\n        private final DensityFunction argument;\n\n        @Json(ignore = true)\n        private DoubleStorage cache;\n\n        public Cache2D(DensityFunction argument) {\n            this.argument = argument;\n        }\n\n        private DoubleStorage cache() {\n            if (cache == null) {\n                cache = DoubleStorage.threadLocal(() -> DoubleStorage.from(argument).cache2d());\n            }\n            return cache;\n        }\n\n        public double compute(Context context) {\n            int blockX = context.blockX();\n            int blockY = context.blockY();\n            int blockZ = context.blockZ();\n            return cache().obtain(blockX, blockY, blockZ);\n        }\n\n        @Override\n        public DensityFunction wrapped() {\n            return argument;\n        }\n    }\n\n    class CacheOnce implements Wrapped {\n\n        private final DensityFunction argument;\n\n        private int lastHash = 0;\n        private double lastValue = 0;\n\n        public CacheOnce(DensityFunction argument) {\n            this.argument = argument;\n        }\n\n        public double compute(Context context) {\n            int blockX = context.blockX();\n            int blockY = context.blockY();\n            int blockZ = context.blockZ();\n            int hash = Objects.hash(blockX, blockY, blockZ);\n            if (this.lastHash != hash) {\n                this.lastValue = this.argument.compute(context);\n                this.lastHash = hash;\n            }\n            return this.lastValue;\n        }\n\n        @Override\n        public DensityFunction wrapped() {\n            return argument;\n        }\n    }\n\n    record CacheAllInCell(DensityFunction wrapped) implements Wrapped {\n        // Used by the game onto final_density and should not be referenced in data packs.\n        // TODO: I have no clue what this means or what it should do\n        public double compute(Context context) {\n            // TODO: Implement\n            throw new UnsupportedOperationException(\"Not implemented\");\n        }\n    }\n\n    record NoiseRoot(double xz_scale, double y_scale, Noise noise) implements DensityFunction {\n\n        @Override\n        public double compute(Context context) {\n            return this.noise.sample(context.x() * this.xz_scale(), context.y() * this.y_scale(), context.z() * this.xz_scale());\n        }\n\n        @Override\n        public double maxValue() {\n            return this.noise.maxValue();\n        }\n\n        @Override\n        public double minValue() {\n            return this.noise.minValue();\n        }\n    }\n\n    class EndIslands implements DensityFunction {\n        private final SimplexNoise islandNoise;\n\n        public EndIslands() {\n            this(0);\n        }\n\n        public EndIslands(long seed) {\n            WorldgenRandom random = WorldgenRandom.legacy(seed);\n            random.consumeInt(17292);\n            this.islandNoise = new SimplexNoise(random);\n        }\n\n        private double calculateHeightScale(int x, int z) {\n            int x0 = x / 2;\n            int z0 = z / 2;\n            int x1 = x % 2;\n            int z1 = z % 2;\n            double f = Util.clamp(100.0 - Math.sqrt((x * x + z * z)) * 8.0, -100.0, 80.0);\n\n            for (int i = -12; i <= 12; ++i) {\n                for (int j = -12; j <= 12; ++j) {\n                    double x2 = x0 + i;\n                    double z2 = z0 + j;\n                    if (x2 * x2 + z2 * z2 > 4096L && this.islandNoise.sample2D(x2, z2) < -0.8999999761581421) {\n                        double f1 = (Math.abs((float) x2) * 3439F + Math.abs((float) z2) * 147F) % 13F + 9F; // we need to use floats here to match vanilla's float overflow\n                        double x3 = x1 - i * 2;\n                        double z3 = z1 - j * 2;\n                        double f2 = Util.clamp(100.0 - Math.sqrt(x3 * x3 + z3 * z3) * f1, -100.0, 80.0);\n                        f = Math.max(f, f2);\n                    }\n                }\n            }\n\n            return f;\n        }\n\n        @Override\n        public double compute(Context context) {\n            return (calculateHeightScale(context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0;\n        }\n\n        @Override\n        public double minValue() {\n            return -0.84375;\n        }\n\n        @Override\n        public double maxValue() {\n            return 0.5625;\n        }\n    }\n\n    record WeirdScaledSampler(DensityFunction input, RarityValueMapper rarity_value_mapper, Noise noise) implements DensityFunction {\n\n        public enum RarityValueMapper {\n            type_1(WeirdScaledSampler::rarityValueMapper1, 2),\n            type_2(WeirdScaledSampler::rarityValueMapper2, 3);\n\n            private final Double2DoubleFunction mapper;\n            private final double maxValue;\n\n            RarityValueMapper(Double2DoubleFunction mapper, double maxValue) {\n                this.mapper = mapper;\n                this.maxValue = maxValue;\n            }\n\n            public Double2DoubleFunction mapper() {\n                return mapper;\n            }\n\n            public double maxValue() {\n                return maxValue;\n            }\n        }\n\n        @Override\n        public double compute(Context context) {\n            double rarity = rarity_value_mapper().mapper().apply(input.compute(context));\n            return rarity * Math.abs(this.noise.sample(context.x() / rarity, context.y() / rarity, context.z() / rarity));\n        }\n\n        @Override\n        public double minValue() {\n            return 0;\n        }\n\n        @Override\n        public double maxValue() {\n            return rarity_value_mapper().maxValue();\n        }\n\n        private static double rarityValueMapper1(double value) {\n            if (value < -0.5) {\n                return 0.75;\n            } else if (value < 0) {\n                return 1;\n            } else if (value < 0.5) {\n                return 1.5;\n            } else {\n                return 2;\n            }\n        }\n\n        private static double rarityValueMapper2(double value) {\n            if (value < -0.75) {\n                return 0.5;\n            } else if (value < -0.5) {\n                return 0.75;\n            } else if (value < 0.5) {\n                return 1;\n            } else if (value < 0.75) {\n                return 2;\n            } else {\n                return 3;\n            }\n        }\n    }\n\n    record Constant(double value) implements DensityFunction {\n        public static final Constant ZERO = new Constant(0);\n        public static Constant ONE = new Constant(1);\n\n        @Override\n        public double compute(Context context) {\n            return value;\n        }\n\n        public double minValue() {\n            return value;\n        }\n\n        public double maxValue() {\n            return value;\n        }\n    }\n\n    record ShiftedNoise(double xz_scale, double y_scale, DensityFunction shift_x, DensityFunction shift_y, DensityFunction shift_z, Noise noise) implements DensityFunction {\n        public double compute(Context context) {\n            return this.noise.sample(\n                    context.x() * this.xz_scale() + this.shift_x.compute(context),\n                    context.y() * this.y_scale() + this.shift_y.compute(context),\n                    context.z() * this.xz_scale() + this.shift_z.compute(context)\n            );\n        }\n\n        @Override\n        public double maxValue() {\n            return noise().maxValue();\n        }\n\n        @Override\n        public double minValue() {\n            return noise().minValue();\n        }\n    }\n\n    record RangeChoice(DensityFunction input, double min_inclusive, double max_exclusive, DensityFunction when_in_range,\n                       DensityFunction when_out_of_range) implements DensityFunction {\n\n        public double compute(Context context) {\n            return this.input.compute(context) >= this.min_inclusive && this.input.compute(context) < this.max_exclusive\n                    ? this.when_in_range.compute(context)\n                    : this.when_out_of_range.compute(context);\n        }\n\n        public double minValue() {\n            return Math.min(this.when_in_range.minValue(), this.when_out_of_range.minValue());\n        }\n\n        public double maxValue() {\n            return Math.max(this.when_in_range.maxValue(), this.when_out_of_range.maxValue());\n        }\n    }\n\n    record ShiftA(Noise argument) implements DensityFunction {\n        // Samples a noise at (x/4, 0, z/4), then multiplies it by 4.\n        public double compute(Context context) {\n            double shiftedX = context.x() * 0.25;\n            double shiftedZ = context.z() * 0.25;\n            return argument.sample(shiftedX, 0, shiftedZ) * 4.0;\n        }\n\n        @Override\n        public double minValue() {\n            return argument.minValue() * 4.0;\n        }\n\n        @Override\n        public double maxValue() {\n            return argument.maxValue() * 4.0;\n        }\n    }\n\n    record ShiftB(Noise argument) implements DensityFunction {\n        // Samples a noise at (z/4, x/4, 0), then multiplies it by 4.\n        public double compute(Context context) {\n            double shiftedX = context.x() * 0.25;\n            double shiftedZ = context.z() * 0.25;\n            return argument.sample(shiftedZ, shiftedX, 0) * 4.0;\n        }\n\n        @Override\n        public double minValue() {\n            return argument.minValue() * 4.0;\n        }\n\n        @Override\n        public double maxValue() {\n            return argument.maxValue() * 4.0;\n        }\n    }\n\n    record Shift(Noise argument) implements DensityFunction {\n        // Samples a noise at (x/4, y/4, z/4), then multiplies it by 4.\n        @Override\n        public double compute(Context context) {\n            double shiftedX = context.x() * 0.25;\n            double shiftedY = context.y() * 0.25;\n            double shiftedZ = context.z() * 0.25;\n            return argument.sample(shiftedX, shiftedY, shiftedZ) * 4.0;\n        }\n\n        @Override\n        public double maxValue() {\n            return argument.maxValue() * 4.0;\n        }\n\n        @Override\n        public double minValue() {\n            return argument.minValue() * 4.0;\n        }\n    }\n\n    record BlendDensity(DensityFunction argument) implements DensityFunction {\n        @Override\n        public double compute(Context context) {\n            // TODO: Actually implement this.\n            return argument.compute(context);\n        }\n\n        public double minValue() {\n            return argument.minValue();\n        }\n\n        public double maxValue() {\n            return argument.maxValue();\n        }\n    }\n\n    record Clamp(double min, double max, DensityFunction input) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.input.compute(context);\n            return Util.clamp(density, this.min, this.max);\n        }\n\n        public double minValue() {\n            return this.min;\n        }\n\n        public double maxValue() {\n            return this.max;\n        }\n    }\n\n    record Abs(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            return Math.abs(density);\n        }\n\n        public double minValue() {\n            // the min value may be higher than 0 if the input's range doesn't include 0\n            if (this.argument.minValue() <= 0 && this.argument.maxValue() >= 0) {\n                return 0;\n            }\n            return Math.min(Math.abs(this.argument.minValue()), Math.abs(this.argument.maxValue()));\n        }\n\n        public double maxValue() {\n            return Math.max(Math.abs(this.argument.minValue()), Math.abs(this.argument.maxValue()));\n        }\n    }\n\n    record Square(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            return Util.square(density);\n        }\n\n        public double minValue() {\n            return Util.square(this.argument.minValue());\n        }\n\n        public double maxValue() {\n            return Util.square(this.argument.maxValue());\n        }\n    }\n\n    record Cube(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            return Util.cube(density);\n        }\n\n        public double minValue() {\n            return Util.cube(this.argument.minValue());\n        }\n\n        public double maxValue() {\n            return Util.cube(this.argument.maxValue());\n        }\n    }\n\n    record HalfNegative(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            return density > 0 ? density : density * 0.5;\n        }\n\n        public double minValue() {\n            return this.argument.minValue() * 0.5;\n        }\n\n        public double maxValue() {\n            return this.argument.maxValue() * 0.5;\n        }\n    }\n\n    record QuarterNegative(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            return density > 0 ? density : density * 0.25;\n        }\n\n        public double minValue() {\n            return this.argument.minValue() * 0.25;\n        }\n\n        public double maxValue() {\n            return this.argument.maxValue();\n        }\n    }\n\n    record Squeeze(DensityFunction argument) implements DensityFunction {\n\n        public double compute(Context context) {\n            double density = this.argument.compute(context);\n            double c = Util.clamp(density, -1, 1);\n            return c / 2.0 - c * c * c / 24.0;\n        }\n\n        public double minValue() {\n            return this.argument.minValue() / 2.0 - this.argument.maxValue() * this.argument.maxValue() * this.argument.maxValue() / 24.0;\n        }\n\n        public double maxValue() {\n            return this.argument.maxValue() / 2.0 - this.argument.minValue() * this.argument.minValue() * this.argument.minValue() / 24.0;\n        }\n    }\n\n    record Add(DensityFunction argument1, DensityFunction argument2) implements DensityFunction {\n        @Override\n        public double compute(Context context) {\n            return this.argument1.compute(context) + this.argument2.compute(context);\n        }\n\n        @Override\n        public double minValue() {\n            return this.argument1.minValue() + this.argument2.minValue();\n        }\n\n        @Override\n        public double maxValue() {\n            return this.argument1.maxValue() + this.argument2.maxValue();\n        }\n    }\n\n    record Mul(DensityFunction argument1, DensityFunction argument2) implements DensityFunction {\n        @Override\n        public double compute(Context context) {\n            return this.argument1.compute(context) * this.argument2.compute(context);\n        }\n\n        @Override\n        public double minValue() {\n            return this.argument1.minValue() * this.argument2.minValue();\n        }\n\n        @Override\n        public double maxValue() {\n            return this.argument1.maxValue() * this.argument2.maxValue();\n        }\n    }\n\n    record Min(DensityFunction argument1, DensityFunction argument2) implements DensityFunction {\n        @Override\n        public double compute(Context context) {\n            return Math.min(this.argument1.compute(context), this.argument2.compute(context));\n        }\n\n        @Override\n        public double minValue() {\n            return Math.min(this.argument1.minValue(), this.argument2.minValue());\n        }\n\n        @Override\n        public double maxValue() {\n            return Math.min(this.argument1.maxValue(), this.argument2.maxValue());\n        }\n    }\n\n    record Max(DensityFunction argument1, DensityFunction argument2) implements DensityFunction {\n        @Override\n        public double compute(Context context) {\n            return Math.max(this.argument1.compute(context), this.argument2.compute(context));\n        }\n\n        @Override\n        public double minValue() {\n            return Math.max(this.argument1.minValue(), this.argument2.minValue());\n        }\n\n        @Override\n        public double maxValue() {\n            return Math.max(this.argument1.maxValue(), this.argument2.maxValue());\n        }\n    }\n\n    record Spline(CubicSpline spline) implements DensityFunction {\n        public double compute(Context context) {\n            return this.spline.compute(context);\n        }\n\n        public double minValue() {\n            return this.spline.min();\n        }\n\n        public double maxValue() {\n            return this.spline.max();\n        }\n    }\n\n    record YClampedGradient(double from_y, double to_y, double from_value, double to_value) implements DensityFunction {\n\n        public double compute(Context context) {\n            return Util.clampedMap(context.y(), this.from_y, this.to_y, this.from_value, this.to_value);\n        }\n\n        public double minValue() {\n            return Math.min(this.from_value, this.to_value);\n        }\n\n        public double maxValue() {\n            return Math.max(this.from_value, this.to_value);\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/FloatProvider.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\n\nimport java.io.IOException;\n\npublic interface FloatProvider {\n    Key type();\n\n    static FloatProvider fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.<FloatProvider>typeMap(reader, token -> switch (token) {\n            case NUMBER -> json -> new Constant((float) json.nextDouble());\n            case BEGIN_OBJECT -> json -> JsonUtils.unionStringTypeAdapted(json, \"type\", type -> switch (type) {\n                case \"minecraft:constant\" -> Constant.class;\n                case \"minecraft:uniform\" -> Uniform.class;\n                case \"minecraft:clamped_normal\" -> ClampedNormal.class;\n                case \"minecraft:trapezoid\" -> Trapezoid.class;\n                default -> null;\n            });\n            default -> null;\n        });\n    }\n\n    //     value: The constant value to use.\n    record Constant(float value) implements FloatProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:constant\");\n        }\n    }\n\n    // Gives a number between two bounds.\n    //     min_inclusive: The minimum possible value (inclusive).\n    //     max_exclusive: The maximum possible value (exclusive). Must be larger than min_inclusive.\n    //\n    record Uniform(Value value) implements FloatProvider {\n        public record Value(float min_inclusive, float max_exclusive) {}\n\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:uniform\");\n        }\n    }\n\n    // Calculated by clamp(normal(mean, deviation), min, max)\n    //\n    //     mean: The mean.\n    //     deviation: The deviation.\n    //     min: The minimum value to clamp to.\n    //     max: The maximum value to clamp to. Must be larger than  min.\n    record ClampedNormal(Value value) implements FloatProvider {\n        public record Value(float mean, float deviation, float min, float max) {}\n\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:clamped_normal\");\n        }\n    }\n\n    //     min: The minimum value.\n    //     max: The maximum value. Must be larger than  min.\n    //     plateau: The range in the middle of the trapezoid distribution that has a uniform distribution. Must be less than or equal to max - min\n    record Trapezoid(Value value) implements FloatProvider {\n        public record Value(float min, float max, float plateau) {}\n\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:trapezoid\");\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/HeightProvider.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.json.Optional;\n\nimport java.io.IOException;\nimport java.util.List;\n\npublic sealed interface HeightProvider {\n\n    Key type();\n\n    static HeightProvider fromJson(JsonReader reader) throws IOException {\n        try (var json = reader.peekJson()) {\n            json.beginObject();\n            if (JsonUtils.findProperty(json, \"type\", JsonReader::nextString) == null) {\n                return new Constant(VerticalAnchor.fromJson(reader));\n            }\n        }\n        return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch (type) {\n            case \"minecraft:constant\" -> Constant.class;\n            case \"minecraft:uniform\" -> Uniform.class;\n            case \"minecraft:biased_to_bottom\" -> BiasedToBottom.class;\n            case \"minecraft:very_biased_to_bottom\" -> VeryBiasedToBottom.class;\n            case \"minecraft:biased_to_top\" -> BiasedToTop.class;\n            case \"minecraft:weighted_list\" -> WeightedList.class;\n            default -> null;\n        });\n    }\n\n    // value: The vertical anchor to use as constant height.\n    record Constant(VerticalAnchor value) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:constant\");\n        }\n    }\n\n    //  min_inclusive: The vertical anchor to use as minimum height.\n    //  max_inclusive: The vertical anchor to use as maximum height.\n    record Uniform(VerticalAnchor min_inclusive, VerticalAnchor max_inclusive) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:uniform\");\n        }\n    }\n\n    //  min_inclusive: The vertical anchor to use as minimum height.\n    //  max_inclusive: The vertical anchor to use as maximum height.\n    //  inner: (optional, defaults to 1) The inner value. Must be at least 1.\n    record BiasedToBottom(VerticalAnchor min_inclusive, VerticalAnchor max_inclusive, @Optional Integer inner) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:biased_to_bottom\");\n        }\n    }\n\n    // min_inclusive: The vertical anchor to use as minimum height.\n    // max_inclusive: The vertical anchor to use as maximum height.\n    // inner: (optional, defaults to 1) The inner value. Must be at least 1.\n    record VeryBiasedToBottom(VerticalAnchor min_inclusive, VerticalAnchor max_inclusive, @Optional Integer inner) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:very_biased_to_bottom\");\n        }\n    }\n\n    // min_inclusive: The vertical anchor to use as minimum height.\n    // max_inclusive: The vertical anchor to use as maximum height.\n    // plateau: (optional, defaults to 0) The length of the range in the middle of the trapezoid distribution that has a uniform distribution.\n    record BiasedToTop(VerticalAnchor min_inclusive, VerticalAnchor max_inclusive, @Optional Integer plateau) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:biased_to_top\");\n        }\n    }\n\n    //  distribution: (Cannot be empty) A random weighted pool of height providers.\n    record WeightedList(List<Entry> distribution) implements HeightProvider {\n        @Override\n        public Key type() {\n            return Key.key(\"minecraft:weighted_list\");\n        }\n\n        // data: A height provider.\n        // weight: The weight of this entry.\n        public record Entry(HeightProvider provider, int weight) {\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/LazyLoadedDensityFunction.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.DatapackUtils;\nimport org.jetbrains.annotations.Nullable;\n\nclass LazyLoadedDensityFunction implements DensityFunction {\n\n    private @Nullable DensityFunction densityFunction = null;\n\n    public LazyLoadedDensityFunction(String id, DatapackLoader.LoadingContext context) {\n        context.whenFinished(finisher -> this.densityFunction = DatapackUtils.findDensityFunction(finisher.datapack(), id)\n                .orElseThrow(() -> new IllegalStateException(\"Density function \" + id + \" not found\")));\n    }\n\n    private DensityFunction densityFunction() {\n        if (densityFunction == null) {\n            throw new IllegalStateException(\"Density function not loaded yet\");\n        }\n        return densityFunction;\n    }\n\n    @Override\n    public double compute(Context context) {\n        return densityFunction().compute(context);\n    }\n\n    @Override\n    public double maxValue() {\n        return densityFunction().maxValue();\n    }\n\n    @Override\n    public double minValue() {\n        return densityFunction().minValue();\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/NoiseSettings.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.squareup.moshi.JsonReader;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.utils.Range;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.worldgen.noise.NormalNoise;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.*;\n\npublic record NoiseSettings(\n        int sea_level,\n        boolean disable_mob_generation,\n        boolean ore_veins_enabled,\n        boolean aquifers_enabled,\n        boolean legacy_random_source,\n        BlockState default_block,\n        BlockState default_fluid,\n        List<SpawnTarget> spawn_target,\n        Noise noise,\n        NoiseRouter noise_router,\n        SurfaceRule surface_rule\n) {\n\n    public static int cellHeight(NoiseSettings settings) {\n        return settings.noise().size_vertical() << 2;\n    }\n    public static int cellWidth(NoiseSettings settings) {\n        return settings.noise().size_horizontal() << 2;\n    }\n    public static double cellCountY(NoiseSettings settings) {\n        return (double) settings.noise().height() / cellHeight(settings);\n    }\n    public static double minCellY(NoiseSettings settings) {\n        return (double) settings.noise().min_y() / cellHeight(settings);\n    }\n\n    // Noise parameter for biome\n    public record SpawnTarget(Range.Float temperature,\n                              Range.Float humidity,\n                              Range.Float continentalness,\n                              Range.Float erosion,\n                              Range.Float weirdness,\n                              Range.Float depth,\n                              float offset) {\n    }\n\n    public record Noise(int min_y,\n                        int height,\n                        int size_horizontal,\n                        int size_vertical) {\n\n        interface SlideSettings {\n            double target();\n\n            double size();\n\n            double offset();\n\n            static SlideSettings fromJson(Object obj) {\n                if (obj instanceof String str)\n                    return SlideSettings.fromJson(new Gson().fromJson(str, JsonObject.class));\n                if (!(obj instanceof JsonObject root))\n                    throw new IllegalStateException(\"Root is not a JsonObject\");\n                double target = Util.<Double>jsonElse(root, \"target\", 0.0, JsonElement::getAsDouble);\n                double size = Util.<Double>jsonElse(root, \"size\", 0.0, JsonElement::getAsDouble);\n                double offset = Util.<Double>jsonElse(root, \"offset\", 0.0, JsonElement::getAsDouble);\n                return new SlideSettings() {\n                    @Override\n                    public double target() {\n                        return target;\n                    }\n\n                    @Override\n                    public double size() {\n                        return size;\n                    }\n\n                    @Override\n                    public double offset() {\n                        return offset;\n                    }\n                };\n            }\n\n            static double apply(SlideSettings slide, double density, double y) {\n                if (slide.size() <= 0) return density;\n                double t = (y - slide.offset()) / slide.size();\n                return Util.clampedLerp(slide.target(), density, t);\n            }\n        }\n    }\n\n    /**\n     *  noise_router: Routes density functions to noise parameters used for world generation. Each field can be an ID of density function or a density function (can be in constant form or object form).\n     *\n     *      initial_density_without_jaggedness: Related to the generation of aquifer and surface rule. At a horizonal position, starting from the top of the world, the game searches from top to bottom with the precision of size_vertical*4 blocks. The first Y-level whose noise value greater than 25/64 is used as the initial terrain height for world generation. This height should be generally lower than the actual terrain height (determined by the final density).\n     *      final_density: Determines where there is an air or a default block. If positive, returns default block which will can be replaced by the  surface_rule. Otherwise, an air where aquifers can generate.\n     *      barrier: Affects whether to separate between aquifers and open areas in caves. Larger values leads to higher probability to separate.\n     *      fluid_level_floodedness: Affects the probability of generating liquid in an cave for aquifer. The larger value leads to higher probability. The noise value greater than 1.0 is regarded as 1.0, and value less than -1.0 is regarded as -1.0.\n     *      fluid_level_spread: Affects the height of the liquid surface at a horizonal position. Smaller value leads to higher probability for lower height.\n     *      lava: Affects whether an aquifer here uses lava instead of water. The threshold is 0.3.\n     *      vein_toggle: Affects ore vein type and vertical range. If the noise value is greater than 0.0, the vein will be a copper vein. If the noise value is less than or equal to 0.0, the vein will be an iron vein.\n     *      vein_ridged: Controls which blocks are part of a vein. If greater than or equal to 0.0, the block will not be part of a vein. If less than 0.0, the block will be either the vein type's stone block, or possibly an ore block.\n     *      vein_gap: Affects which blocks in a vein will be ore blocks. If greater than -0.3, and a random number is less than the absolute value of vein_toggle mapped from 0.4 - 0.6 to 0.1 - 0.3, with values outside of this range clamped, an ore block will be placed, with a 2% chance for the ore block to be a raw metal block. Otherwise, the ore type's stone block will be placed.\n     *      temperature: The temperature values for biome placement. This field and the following five fields are used for biome placement.\n     *      vegetation: The humidity values for biome placement.\n     *      continents: The continentalness values for terrain generation and biome placement.\n     *      erosion: The erosion values for terrain generation and biome placement.\n     *      depth: The depth values for terrain generation and biome placement.\n     *      ridges: The weirdness values for terrain generation and biome placement.\n     */\n    public record NoiseRouter(\n            DensityFunction initial_density_without_jaggedness,\n            DensityFunction final_density,\n            DensityFunction barrier,\n            DensityFunction fluid_level_floodedness,\n            DensityFunction fluid_level_spread,\n            DensityFunction lava,\n            DensityFunction vein_toggle,\n            DensityFunction vein_ridged,\n            DensityFunction vein_gap,\n            DensityFunction temperature,\n            DensityFunction vegetation,\n            DensityFunction continents,\n            DensityFunction erosion,\n            DensityFunction depth,\n            DensityFunction ridges\n    ) {\n        static final Map<String, NormalNoise> noiseCache = Collections.synchronizedMap(new HashMap<>());\n\n        public static NormalNoise instantiate(WorldgenRandom.Positional random, NormalNoise.Config params) {\n            var randomKey = random.seedKey();\n            var cacheMapKey = Objects.hash(randomKey[0], randomKey[1]) + \"|\" + params.hashCode();\n            return noiseCache.computeIfAbsent(cacheMapKey, k -> new NormalNoise(random.fromSeed(params.hashCode()), params));\n        }\n    }\n\n\n    /**\n     * Surface rule\n     *  type: Type of the surface rule, one of: bandlands [sic], block, condition, or sequence. See below of extra fields for each type.\n     * If  type is bandlands [sic] (used in badlands), no extra fields.\n     * If  type is blocks (places the specified block), extra fields are as follows:\n     *  result_state: The block to use.\n     * If  type is sequence (attempts to apply surface rules in order, and only the first successful surface rule is applied), extra fields are as follows:\n     *  sequence: (Required, but can be empty) List of surface rules.\n     *     : A surface rule.\n     * If  type is condition (applies surface rules if the condition is met), extra fields are as follows:\n     *  if_true: A surface rule condition.\n     *  then_run: A surface rule.\n     */\n    public interface SurfaceRule {\n        Key type();\n\n        Pos2Block apply(Context context);\n\n        interface Context extends VerticalAnchor.Context {\n            Key biome();\n\n            int minY();\n            int maxY();\n\n            int blockX();\n            int blockY();\n            int blockZ();\n\n            WorldgenRandom random(String string);\n\n            // misc surface details\n            int stoneDepthAbove();\n            int surfaceDepth();\n            int waterHeight();\n            int minSurfaceLevel();\n            int stoneDepthBelow();\n            double surfaceSecondary();\n        }\n\n        interface Pos2Block {\n            @Nullable Block apply(int x, int y, int z);\n        }\n\n        static SurfaceRule fromJson(JsonReader reader) throws IOException {\n            return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch (type) {\n                case \"minecraft:bandlands\" -> Bandlands.class;\n                case \"minecraft:block\" -> Blocks.class;\n                case \"minecraft:sequence\" -> Sequence.class;\n                case \"minecraft:condition\" -> Condition.class;\n                default -> null;\n            });\n        }\n\n        record Bandlands() implements SurfaceRule {\n            @Override\n            public Key type() {\n                return Key.key(\"bandlands\");\n            }\n\n            @Override\n            public Pos2Block apply(Context context) {\n                // TODO: implement\n                throw new UnsupportedOperationException(\"Not implemented\");\n            }\n        }\n\n        record Blocks(BlockState result_state) implements SurfaceRule {\n            @Override\n            public Key type() {\n                return Key.key(\"blocks\");\n            }\n\n            @Override\n            public Pos2Block apply(Context context) {\n                return (x, y, z) -> result_state().toMinestom();\n            }\n        }\n\n        record Sequence(List<SurfaceRule> sequence) implements SurfaceRule {\n            @Override\n            public Key type() {\n                return Key.key(\"sequence\");\n            }\n\n            @Override\n            public Pos2Block apply(Context context) {\n                List<Pos2Block> rulesWithContext = sequence().stream()\n                        .map(rule -> rule.apply(context))\n                        .toList();\n                return (x, y, z) -> {\n                    for (Pos2Block rule : rulesWithContext) {\n                        Block result = rule.apply(x, y, z);\n                        if (result != null) return result;\n                    }\n                    return null;\n                };\n            }\n        }\n\n        record Condition(SurfaceRuleCondition if_true, SurfaceRule then_run) implements SurfaceRule {\n            @Override\n            public Key type() {\n                return Key.key(\"condition\");\n            }\n\n            @Override\n            public Pos2Block apply(Context context) {\n                return (x, y, z) -> {\n                    if (if_true().test(context)) {\n                        return then_run().apply(context).apply(x, y, z);\n                    }\n                    return null;\n                };\n            }\n        }\n\n        /**\n         * Surface rule condition\n         *  type: Type of the surface rule, one of: biome, noise_threshold, vertical_gradient, y_above, water, temperature, steep, not, hole, above_preliminary_surface, or stone_depth. See below of extra fields for each type.\n         * If  type is biome (test for the biome), extra fields are as follows:\n         *  biome_is: (Required, but can be empty) List of biomes that result in true.\n         *     : The ID of a biome.\n         * If  type is noise_threshold (Success when the noise value at this XZ losction with Y=0 is within the specified closed interval), extra fields are as follows:\n         *  noise: The ID of a noise.\n         *  min_threshold: Min threshold of the closed interval.\n         *  max_threshold: Max threshold of the closed interval.\n         * If  type is vertical_gradient (Makes the block fade upwards. Between the specified y-coords is the gradient itself. For example the gradient between bedrock and deepslate, or between deepslate and stone), extra fields are as follows:\n         *  random_name: A namespace ID used as the seed of the random. For example, the seed between bedrock and deepslate in the vanilla game is \"minecraft:bedrock_floor\", and the seed between deepslate and stone is \"minecraft:deepslate\".\n         *  true_at_and_below: Always succcess if the y-coord is at or below this value.\n         *     Choices for a vertical anchor (must choose only one of three)\n         *  false_at_and_above: Always fails if the y-coord is at or above this value. The y-coords between the two value produces a gradient, and the probability of success in this gradient is (false_at_and_above - Y) / (false_at_and_above - true_at_and_below)\n         *     Choices for a vertical anchor (must choose only one of three)\n         * If  type is y_above (checks if it is above a XZ plane at the specified Y level. E.g. block whose Y coordinate is 0 is above Y=0 plane), extra fields are as follows:\n         *  anchor: Y level.\n         *     Choices for a vertical anchor (must choose only one of three)\n         *  surface_depth_multiplier: Value between -20 and 20 (both inclusive). How much it is affected by the surface layer thickness. surfaceLayerThickness * surface_depth_multiplier will be added into anchor.\n         *  add_stone_depth: Instead of current block's Y-level, checks the value of \"current block's Y-level\" plus \"the number of non-liquid blocks between current block's downward surface and the lowest air block directly above\". For example, if block at Y=2 is air, Y=1 is water, and Y=0 is stone, when applied at the stone, the number of non-liquid blocks between current block's downward surface (in this case, Y=0 plane) and the lowest air block directly above (in this case, air at Y=2) is 1 (that is, this stone itself).\n         * If  type is water (Check whether the offset height of the current block relative to the liquid surface (the contact surface between air and liquid) above (always a negative integer less than -1) is greater than the specified value. Always success if there's no liquid between them. For example, if there is only one liquid block between current block and the air block above, the value to check is -2), extra fields are as follows:\n         *  offset: The offset height relative to the liquid surface (the contact surface between air and liquid) above. If it is set to a value greater than -1, the condition is successful only if there is no liquid between current block and the lowest air block above. If it is set to -1, it works the same with values greater than -1 in terrain generation, and always successful in carver generation.\n         *  surface_depth_multiplier: Value between -20 and 20 (both inclusive). How much it is affected by the surface layer thickness. surfaceLayerThickness * surface_depth_multiplier will be added into the offset.\n         *  add_stone_depth: Instead of current block's Y-level, checks the value of \"current block's Y-level\" plus \"the number of non-liquid blocks between current block's downward surface and the lowest air block directly above\". For example, if block at Y=2 is air, Y=1 is water, and Y=0 is stone, when applied at the stone, the number of non-liquid blocks between current block's downward surface (in this case, Y=0 plane) and the lowest air block directly above (in this case, air at Y=2) is 1 (that is, this stone itself).\n         * If  type is temperature (success when the height-adjusted temperature is low enough to snow. The height-adjusted temperature depends on the biome's temperature and temperature_modifier fields and the current Y-level), no extra fields.\n         * If  type is steep (checks current position for steep slopes (with height difference of more than 4 blocks) that are back sun (north or east facing)), no extra fields.\n         * If  type is not (inverts the condition), extra fields are as follows:\n         *  invert: The condition to invert.\n         *     Surface rule condition\n         * If  type is hole (check whether the surface layer thickness at this horizonal location is less than 0), no extra fields.\n         * If  type is above_preliminary_surface (checks whether it is higher than the preliminary surface. The preliminary surface height is the interpolated initial terrain height (determined by initial_density_without_jaggedness) minus 8 and then plus (surfaceLayerThickness - 8)), no extra fields.\n         * If  type is stone_depth (checks whether the distance between the current position and the terrain surface or the cave surface is less than or equal to the specified offset value), extra fields are as follows:\n         *  offset: The offset value.\n         *  add_surface_depth: Whether to be affected by surface layer thickness. If true, the surface layer thickness will be addded into the offset.\n         *  secondary_depth_range: How much it is affected by the noise minecraft:surface_secondary. niseValue × secondary_depth_range will be added into the offset.\n         *  surface_type: Either floor or ceiling. If ceiling, checks the distance to the upper surface of cave below (technically, it is the distance to the nearest liquid or air block directly below). For example, if where Y=-1 is water, and where Y=0 is stone, when applied to the stone, the distance to the nearest liquid or air block directly below (in this case, the water at Y=-1) is 0. If it isfloor, checks the distance to the terrain surface or the lower surface of cave above (technically, it is the number of non-liquid blocks between current block and the lowest air block directly above. If there is liquid between current block and the air block above, this value may be less than the actual distance to the surface of terrain or cave). For example, where Y=2 is air, Y=1 is water, and Y=0 is stone, when applying this condition at the stone, the number of non-liquid blocks between current block and the lowest air block directly above (in this case, air at Y=2) is 0.\n         */\n        interface SurfaceRuleCondition {\n            Key type();\n\n            boolean test(SurfaceRule.Context context);\n\n            static SurfaceRuleCondition fromJson(JsonReader reader) throws IOException {\n                return JsonUtils.unionStringTypeAdapted(reader, \"type\", type -> switch (type) {\n                    case \"minecraft:biome\" -> Biome.class;\n                    case \"minecraft:noise_threshold\" -> NoiseThreshold.class;\n                    case \"minecraft:vertical_gradient\" -> VerticalGradient.class;\n                    case \"minecraft:y_above\" -> YAbove.class;\n                    case \"minecraft:water\" -> Water.class;\n                    case \"minecraft:temperature\" -> Temperature.class;\n                    case \"minecraft:steep\" -> Steep.class;\n                    case \"minecraft:not\" -> Not.class;\n                    case \"minecraft:hole\" -> Hole.class;\n                    case \"minecraft:above_preliminary_surface\" -> AbovePreliminarySurface.class;\n                    case \"minecraft:stone_depth\" -> StoneDepth.class;\n                    default -> null;\n                });\n            }\n\n            record Biome(List<Key> biome_is) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"biome\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    return biome_is.contains(context.biome());\n                }\n            }\n\n            record NoiseThreshold(Key noise, double min_threshold, double max_threshold) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"noise_threshold\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    // TODO: Implement this\n                    throw new UnsupportedOperationException(\"Not implemented yet\");\n                }\n            }\n\n            record VerticalGradient(Key random_name, VerticalAnchor true_at_and_below, VerticalAnchor false_at_and_above) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"vertical_gradient\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    int trueAtAndBelowY = true_at_and_below().apply(context);\n                    int falseAtAndAboveY = false_at_and_above().apply(context);\n                    if (context.blockY() <= trueAtAndBelowY) {\n                        return true;\n                    }\n                    if (context.blockY() >= falseAtAndAboveY) {\n                        return false;\n                    }\n                    WorldgenRandom random = context.random(random_name().toString());\n                    double chance = Util.map(context.blockY(), trueAtAndBelowY, falseAtAndAboveY, 1, 0);\n                    return random.nextFloat() < chance;\n                }\n            }\n\n            record YAbove(VerticalAnchor anchor, int surface_depth_multiplier, boolean add_stone_depth) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"y_above\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    int stoneDepth = add_stone_depth() ? context.stoneDepthAbove() : 0;\n                    return context.blockY() + stoneDepth >= anchor.apply(context) + context.surfaceDepth() * surface_depth_multiplier();\n                }\n            }\n\n            record Water(int offset, int surface_depth_multiplier, boolean add_stone_depth) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"water\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    if (context.waterHeight() == Integer.MIN_VALUE) {\n                        return true;\n                    }\n                    int stoneDepth = add_stone_depth() ? context.stoneDepthAbove() : 0;\n                    return context.blockY() + stoneDepth >= context.waterHeight() + offset() + context.surfaceDepth() * surface_depth_multiplier();\n                }\n            }\n\n            record Temperature() implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"temperature\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    // TODO: Implement this\n                    throw new UnsupportedOperationException(\"Not implemented yet\");\n                }\n            }\n\n            record Steep() implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"steep\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    // TODO: Implement this\n                    throw new UnsupportedOperationException(\"Not implemented yet\");\n                }\n            }\n\n            record Not(SurfaceRuleCondition invert) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"not\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    return !invert.test(context);\n                }\n            }\n\n            record Hole() implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"hole\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    // TODO: Implement this\n                    throw new UnsupportedOperationException(\"Not implemented yet\");\n                }\n            }\n\n            record AbovePreliminarySurface() implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"above_preliminary_surface\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    return context.blockY() >= context.minSurfaceLevel();\n                }\n            }\n\n            record StoneDepth(int offset, boolean add_surface_depth, int secondary_depth_range, SurfaceType surface_type) implements SurfaceRuleCondition {\n                @Override\n                public Key type() {\n                    return Key.key(\"stone_depth\");\n                }\n\n                @Override\n                public boolean test(SurfaceRule.Context context) {\n                    int depth = switch (surface_type()) {\n                        case ceiling -> context.stoneDepthBelow();\n                        case floor -> context.stoneDepthAbove();\n                    };\n                    int surfaceDepth = add_surface_depth() ? context.surfaceDepth() : 0;\n                    int secondaryDepth = secondary_depth_range() == 0 ? 0 : (int) Util.map(context.surfaceSecondary(), -1, 1, 0, secondary_depth_range());\n                    return depth <= 1 + offset + surfaceDepth + secondaryDepth;\n                }\n\n                enum SurfaceType {\n                    floor,\n                    ceiling\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/Structure.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.kyori.adventure.nbt.*;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.vanilla.files.ByteArray;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.UnknownNullability;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\n\n/**\n * DataVersion: Data version of the NBT structure.\n * author: Name of the player who created this structure. Only exists for structures saved before 1.13.\n * size: 3 TAG_Int describing the size of the structure.\n * palette: Set of different block states used in the structure.\n * A block.\n * Name: Block ID.\n * Properties: List of block state properties, with [name] being the name of the block state property.\n * Name: The block state name and its value.\n * palettes: Sets of different block states used in the structure, a random palette gets selected based on coordinates. Used in vanilla by shipwrecks.\n * A set of different block states used in the structure.\n * A block.\n * Name: Block ID.\n * Properties: List of block state properties, with [name] being the name of the block state property.\n * Name: The block state name and its value.\n * blocks: List of individual blocks in the structure.\n * An individual block.\n * state: Index of the block in the palette.\n * pos: 3 TAG_Int describing the position of this block.\n * nbt: NBT of the associated block entity (optional, only present if the block has one). Does not contain x, y, or z fields. See Block entity format.\n * entities: List of entities in the structure.\n * An entity.\n * pos: 3 TAG_Double describing the exact position of the entity.\n * blockPos: 3 TAG_Int describing the block position of the entity.\n * nbt: NBT of the entity (required). See entity format.\n *\n * @param DataVersion Data version of the NBT structure.\n * @param author      Name of the player who created this structure. Only exists for structures saved before 1.13.\n * @param size        3 TAG_Int describing the size of the structure.\n * @param palette     Set of different block states used in the structure.\n * @param palettes    Sets of different block states used in the structure, a random palette gets selected based on coordinates. Used in vanilla by shipwrecks.\n * @param blocks      List of individual blocks in the structure.\n * @param entities    List of entities in the structure.\n */\npublic record Structure(int DataVersion, @Nullable String author, Point size,\n                        @Nullable Set<BlockState> palette, @UnknownNullability Set<Set<BlockState>> palettes,\n                        List<Block> blocks, List<Entity> entities) {\n    public static Structure fromInput(ByteArray content) {\n\n        try {\n            CompoundBinaryTag root = BinaryTagIO.reader().read(content.toStream());\n\n            Objects.requireNonNull(root);\n\n            int DataVersion = root.getInt(\"DataVersion\");\n            @Nullable String author = root.getString(\"author\");\n\n            ListBinaryTag nbt_size = Objects.requireNonNull(root.getList(\"size\"));\n            ListBinaryTag nbt_palette = root.getList(\"palette\");\n            ListBinaryTag nbt_palettes = root.getList(\"palettes\");\n            ListBinaryTag nbt_blocks = Objects.requireNonNull(root.getList(\"blocks\"));\n            ListBinaryTag nbt_entities = Objects.requireNonNull(root.getList(\"entities\"));\n\n            Point size = parsePoint(nbt_size);\n            // Only one of \"palette\" OR \"palettes\" is present\n            Set<BlockState> palette = nbt_palette == null ? null : parsePalette(nbt_palette);\n            Set<Set<BlockState>> palettes = nbt_palettes == null ? null : parsePalettes(nbt_palettes);\n            List<Block> blocks = parseBlocks(nbt_blocks);\n            List<Entity> entities = parseEntities(nbt_entities);\n\n            return new Structure(DataVersion, author, size, palette, palettes, blocks, entities);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static Point parsePoint(ListBinaryTag nbtSize) {\n        return new Vec(((IntBinaryTag) nbtSize.get(0)).value(), ((IntBinaryTag) nbtSize.get(1)).value(), ((IntBinaryTag) nbtSize.get(2)).value());\n    }\n\n    private static Point parseDoublePoint(ListBinaryTag nbtSize) {\n        return new Vec(((DoubleBinaryTag) nbtSize.get(0)).value(), ((DoubleBinaryTag) nbtSize.get(1)).value(), ((DoubleBinaryTag) nbtSize.get(2)).value());\n    }\n\n    private static BlockState parseBlockState(CompoundBinaryTag block) {\n        String blockId = Objects.requireNonNull(block.getString(\"Name\"));\n        CompoundBinaryTag properties = block.getCompound(\"Properties\");\n        return new BlockState(\n                blockId,\n                StreamSupport.stream(properties.spliterator(), false)\n                        .map(entry -> Map.entry(entry.getKey(), (StringBinaryTag) entry.getValue()))\n                        .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().value()))\n        );\n    }\n\n    private static Set<BlockState> parsePalette(ListBinaryTag nbtPalette) {\n        return nbtPalette.stream()\n                .map(CompoundBinaryTag.class::cast)\n                .map(Structure::parseBlockState)\n                .collect(Collectors.toSet());\n    }\n\n    private static Set<Set<BlockState>> parsePalettes(ListBinaryTag nbtPalettes) {\n        return nbtPalettes.stream()\n                .map(ListBinaryTag.class::cast)\n                .map(palette -> palette.stream()\n                        .map(CompoundBinaryTag.class::cast)\n                        .map(Structure::parseBlockState)\n                        .collect(Collectors.toSet()))\n                .collect(Collectors.toSet());\n    }\n\n    private static List<Block> parseBlocks(ListBinaryTag nbtBlocks) {\n        return nbtBlocks.stream()\n                .map(CompoundBinaryTag.class::cast)\n                .map(block -> new Block(\n                        Objects.requireNonNull(block.getInt(\"state\")),\n                        parsePoint(Objects.requireNonNull(block.getList(\"pos\"))),\n                        block.get(\"nbt\")\n                ))\n                .collect(Collectors.toList());\n    }\n\n    private static List<Entity> parseEntities(ListBinaryTag nbtEntities) {\n        return nbtEntities.stream()\n                .map(CompoundBinaryTag.class::cast)\n                .map(entity -> new Entity(\n                        parseDoublePoint(Objects.requireNonNull(entity.getList(\"pos\"))),\n                        parsePoint(Objects.requireNonNull(entity.getList(\"blockPos\"))),\n                        Objects.requireNonNull(entity.getCompound(\"nbt\"))\n                ))\n                .collect(Collectors.toList());\n    }\n\n    public record Block(int state, Point pos, @Nullable BinaryTag nbt) {\n    }\n\n    public record Entity(Point pos, Point blockPos, BinaryTag nbt) {\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/VerticalAnchor.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.squareup.moshi.JsonReader;\n\nimport java.io.IOException;\n\nsealed public interface VerticalAnchor {\n\n    // context here is for optimization potential\n    interface Context {\n        int minY();\n        int maxY();\n    }\n\n    int apply(Context context);\n\n    static VerticalAnchor fromJson(JsonReader reader) throws IOException {\n        // Vertical anchor is a special case...\n        // thx mojang!\n        reader.beginObject();\n        String type = reader.nextName();\n        int value = reader.nextInt();\n        reader.endObject();\n\n        return switch (type) {\n            case \"absolute\" -> new Absolute(value);\n            case \"above_bottom\" -> new AboveBottom(value);\n            case \"below_top\" -> new BelowTop(value);\n            default -> throw new IllegalStateException(\"Unexpected value: \" + type);\n        };\n    }\n\n    record Absolute(int value) implements VerticalAnchor {\n        @Override\n        public int apply(Context context) {\n            return value;\n        }\n    }\n\n    record AboveBottom(int offset) implements VerticalAnchor {\n        @Override\n        public int apply(Context context) {\n            return context.minY() + offset;\n        }\n    }\n\n    record BelowTop(int offset) implements VerticalAnchor {\n        @Override\n        public int apply(Context context) {\n            return context.maxY() - offset;\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/WorldgenContext.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.minestom.server.world.DimensionType;\n\npublic interface WorldgenContext {\n\n    static WorldgenContext create(DimensionType dimension) {\n        return () -> dimension;\n    }\n\n    default int minY() {\n        return dimension().minY();\n    }\n    default int maxY() {\n        return dimension().maxY();\n    }\n\n    DimensionType dimension();\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/WorldgenRegistries.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\nimport net.minestom.vanilla.datapack.worldgen.noise.NormalNoise;\n\npublic class WorldgenRegistries {\n    public static final NormalNoise.Config SURFACE_NOISE = new NormalNoise.Config(-6, DoubleList.of(1, 1, 1));\n    public static final NormalNoise.Config SURFACE_SECONDARY_NOISE = new NormalNoise.Config(-6, DoubleList.of(1, 1, 0, 1));\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/biome/BiomeSource.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.biome;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\npublic interface BiomeSource extends BiomeSources {\n//    export interface BiomeSource {\n//        getBiome(x: number, y: number, z: number, climateSampler: Climate.Sampler): Identifier\n//    }\n\n    Key getBiome(int x, int y, int z, Climate.Sampler climateSampler);\n\n//    export namespace BiomeSource {\n//        export function fromJson(obj: unknown): BiomeSource {\n//\t\tconst root = Json.readObject(obj) ?? {}\n//\t\tconst type = Json.readString(root.type)?.replace(/^minecraft:/, '')\n//            switch (type) {\n//                case 'fixed': return FixedBiomeSource.fromJson(obj)\n//                case 'checkerboard': return CheckerboardBiomeSource.fromJson(obj)\n//                case 'multi_noise': return MultiNoiseBiomeSource.fromJson(obj)\n//                case 'the_end': return TheEndBiomeSource.fromJson(obj)\n//                default: return { getBiome: () => Identifier.create('plains') }\n//            }\n//        }\n//    }\n\n    static BiomeSource checkerBoard(int shift, Key... biomes) {\n        return new CheckerboardBiomeSource(shift, biomes);\n    }\n\n    static BiomeSource fixed(Key biome) {\n        return new FixedBiomeSource(biome);\n    }\n\n    static BiomeSource multiNoise(Climate.Parameters<Key> parameters) {\n        return new MultiNoiseBiomeSource(parameters);\n    }\n\n    static BiomeSource theEnd() {\n        return new TheEndBiomeSource();\n    }\n\n    static BiomeSource fromJson(Object obj) {\n        JsonObject root = Util.jsonObject(obj);\n\n        String type = Util.jsonRequire(root, \"type\", JsonElement::getAsString).replace(\"^minecraft:\", \"\");\n        return switch (type) {\n            case \"fixed\" -> FixedBiomeSource.fromJson(obj);\n            case \"checkerboard\" -> CheckerboardBiomeSource.fromJson(obj);\n            case \"multi_noise\" -> MultiNoiseBiomeSource.fromJson(obj);\n            case \"the_end\" -> TheEndBiomeSource.fromJson(obj);\n            default -> (x, y, z, climateSampler) -> Key.key(\"plains\");\n        };\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/biome/BiomeSources.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.biome;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\n\ninterface BiomeSources {\n\n    record CheckerboardBiomeSource(int n, int shift, List<Key> biomes) implements BiomeSource {\n\n        public CheckerboardBiomeSource(int shift, Key[] biomes) {\n            this(biomes.length, shift, List.of(biomes));\n        }\n\n        public CheckerboardBiomeSource {\n            if (biomes.isEmpty()) {\n                throw new IllegalArgumentException(\"Cannot create checkerboard biome source without biomes\");\n            }\n        }\n\n        @Override\n        public Key getBiome(int x, int y, int z, Climate.Sampler climateSampler) {\n            int i = (((x >> this.shift) + (z >> this.shift)) % this.n + this.n) % this.n;\n            return this.biomes.get(i);\n        }\n\n        public static CheckerboardBiomeSource fromJson(Object obj) {\n            int scale = Util.jsonElse(Util.jsonObject(obj), \"scale\", 2, JsonElement::getAsInt);\n            Key[] biomes;\n            if (obj instanceof String) {\n                biomes = new Key[]{Key.key((String) obj)};\n            } else if (obj instanceof JsonElement) {\n                biomes = Util.jsonArray((JsonElement) obj, element -> Key.key(element.getAsString())).toArray(new Key[0]);\n            } else {\n                throw new IllegalArgumentException(\"Cannot parse biome source from \" + obj);\n            }\n            return new CheckerboardBiomeSource(scale + 2, biomes);\n        }\n    }\n\n    record FixedBiomeSource(Key biome) implements BiomeSource {\n\n        @Override\n        public Key getBiome(int x, int y, int z, Climate.Sampler climateSampler) {\n            return this.biome;\n        }\n\n        public static FixedBiomeSource fromJson(Object obj) {\n            JsonObject root = Util.jsonObject(obj);\n            Key biome = Key.key(Util.jsonElse(root, \"biome\", \"plains\", JsonElement::getAsString));\n            return new FixedBiomeSource(biome);\n        }\n    }\n\n    record MultiNoiseBiomeSource(Climate.Parameters<Key> parameters) implements BiomeSource {\n\n        @Override\n        public Key getBiome(int x, int y, int z, Climate.Sampler climateSampler) {\n            Climate.TargetPoint target = climateSampler.sample(x, y, z);\n            return this.parameters.find(target);\n        }\n\n        public static MultiNoiseBiomeSource fromJson(Object obj) {\n            JsonObject root = Util.jsonObject(obj);\n            JsonArray biomes = Util.jsonArray(root.get(\"biomes\"));\n\n            var biomesList = StreamSupport.stream(biomes.spliterator(), false).map(b -> {\n                JsonObject json = Util.jsonObject(b);\n                var biomeName = Key.key(Util.jsonRequire(json, \"biome\", JsonElement::getAsString));\n                var parameters = Climate.ParamPoint.fromJson(json.get(\"parameters\"));\n                return Map.entry(biomeName, parameters);\n            }).toList();\n\n            Map<Climate.ParamPoint, Supplier<Key>> parameters = biomesList.stream().collect(Collectors.toMap(\n                    Map.Entry::getValue,\n                    e -> e::getKey\n            ));\n\n            return new MultiNoiseBiomeSource(new Climate.Parameters<>(parameters));\n        }\n    }\n\n    record TheEndBiomeSource() implements BiomeSource {\n        private static final Key END = Key.key(\"the_end\");\n        private static final Key HIGHLANDS = Key.key(\"end_highlands\");\n        private static final Key MIDLANDS = Key.key(\"end_midlands\");\n        private static final Key ISLANDS = Key.key(\"small_end_islands\");\n        private static final Key BARRENS = Key.key(\"end_barrens\");\n\n        @Override\n        public Key getBiome(int x, int y, int z, Climate.Sampler climateSampler) {\n            int blockX = x << 2;\n            int blockY = y << 2;\n            int blockZ = z << 2;\n\n            int sectionX = blockX >> 4;\n            int sectionZ = blockZ >> 4;\n\n            if (sectionX * sectionX + sectionZ * sectionZ <= 4096) {\n                return END;\n            }\n\n            DensityFunction.Context context = DensityFunction.context((sectionX * 2 + 1) * 8, blockY, (sectionZ * 2 + 1) * 8);\n            double erosion = climateSampler.erosion().compute(context);\n\n            if (erosion > 0.25) {\n                return HIGHLANDS;\n            } else if (erosion >= -0.0625) {\n                return MIDLANDS;\n            } else if (erosion >= -0.21875) {\n                return BARRENS;\n            } else {\n                return ISLANDS;\n            }\n        }\n\n        public static TheEndBiomeSource fromJson(Object obj) {\n            return new TheEndBiomeSource();\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/biome/Climate.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.biome;\n\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.util.*;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\n\npublic class Climate {\n    static final int PARAMETER_SPACE = 7;\n\n    //        export function target(temperature: number, humidity: number, continentalness: number, erosion: number, depth: number, weirdness: number) {\n//        return new TargetPoint(temperature, humidity, continentalness, erosion, depth, weirdness)\n//        }\n    public static TargetPoint target(double temperature, double humidity, double continentalness, double erosion, double depth, double weirdness) {\n        return new TargetPoint(temperature, humidity, continentalness, erosion, depth, weirdness);\n    }\n\n    //        export function parameters(temperature: number | Param, humidity: number | Param, continentalness: number | Param, erosion: number | Param, depth: number | Param, weirdness: number | Param, offset: number) {\n//            return new ParamPoint(param(temperature), param(humidity), param(continentalness), param(erosion), param(depth), param(weirdness), offset)\n//        }\n    public static ParamPoint parameters(double temperature, double humidity, double continentalness, double erosion, double depth, double weirdness, double offset) {\n        return new ParamPoint(param(temperature), param(humidity), param(continentalness), param(erosion), param(depth), param(weirdness), offset);\n    }\n\n    //        export function param(value: number | Param, max?: number) {\n//        if (typeof value === 'number') {\n//        return new Param(value, max ?? value)\n//        }\n//        return value\n//        }\n    public static Param param(double min, double max) {\n        return new Param(min, max);\n    }\n\n    public static Param param(double value) {\n        return new Param(value, value);\n    }\n\n    public static Param param(Param value) {\n        return value;\n    }\n\n    public static Param param(Object value) {\n        if (value instanceof Param) {\n            return (Param) value;\n        }\n        if (value instanceof Number) {\n            return new Param(((Number) value).doubleValue(), ((Number) value).doubleValue());\n        }\n        throw new IllegalArgumentException(\"Cannot convert \" + value + \" to Param\");\n    }\n\n    //        export class Param {\n//    constructor(\n//            public readonly min: number,\n//            public readonly max: number,\n//            ) {}\n    public record Param(double min, double max) {\n        //    public distance(param: Param | number) {\n//\t\t\tconst diffMax = (typeof param === 'number' ? param : param.min) - this.max\n//\t\t\tconst diffMin = this.min - (typeof param === 'number' ? param : param.max)\n//        if (diffMax > 0) {\n//            return diffMax\n//        }\n//        return Math.max(diffMin, 0)\n//    }\n        public double distance(Param param) {\n            double diffMax = param.min() - this.max();\n            double diffMin = this.min() - param.max();\n            if (diffMax > 0) {\n                return diffMax;\n            }\n            return Math.max(diffMin, 0);\n        }\n\n        //    public union(param: Param) {\n//        return new Param(\n//                Math.min(this.min, param.min),\n//                Math.max(this.max, param.max)\n//        )\n//    }\n        public Param union(Param param) {\n            return new Param(\n                    Math.min(this.min(), param.min()),\n                    Math.max(this.max(), param.max())\n            );\n        }\n\n        //    public static fromJson(obj: unknown) {\n//        if (typeof obj === 'number') return new Param(obj, obj)\n//\t\t\tconst [min, max] = Json.readArray(obj, e => Json.readNumber(e)) ?? []\n//        return new Param(min ?? 0, max ?? 0)\n//    }\n        public static Param fromJson(Object obj) {\n            if (obj instanceof JsonElement json) {\n                if (json.isJsonPrimitive()) {\n                    return fromJson(json.getAsDouble());\n                }\n                if (json.isJsonArray()) {\n                    double[] array = StreamSupport.stream(json.getAsJsonArray().spliterator(), false)\n                            .mapToDouble(JsonElement::getAsDouble)\n                            .toArray();\n                    return new Param(array[0], array[1]);\n                }\n            }\n            if (obj instanceof Number) {\n                return new Param(((Number) obj).doubleValue(), ((Number) obj).doubleValue());\n            }\n            throw new IllegalArgumentException(\"Cannot convert \" + obj + \" to Param\");\n        }\n    }\n\n    //\texport class ParamPoint {\n//    constructor(\n//            public readonly temperature: Param,\n//            public readonly humidity: Param,\n//            public readonly continentalness: Param,\n//            public readonly erosion: Param,\n//            public readonly depth: Param,\n//            public readonly weirdness: Param,\n//            public readonly offset: number,\n//            ) {}\n    public record ParamPoint(Param temperature, Param humidity, Param continentalness, Param erosion, Param depth,\n                             Param weirdness, double offset) {\n        public double fittness(ParamPoint point) {\n            return Util.square(this.temperature().distance(point.temperature()))\n                    + Util.square(this.humidity().distance(point.humidity()))\n                    + Util.square(this.continentalness().distance(point.continentalness()))\n                    + Util.square(this.erosion().distance(point.erosion()))\n                    + Util.square(this.depth().distance(point.depth()))\n                    + Util.square(this.weirdness().distance(point.weirdness()))\n                    + Util.square(this.offset() - point.offset());\n        }\n\n\n//    public fittness(point: ParamPoint | TargetPoint) {\n//        return square(this.temperature.distance(point.temperature))\n//                + square(this.humidity.distance(point.humidity))\n//                + square(this.continentalness.distance(point.continentalness))\n//                + square(this.erosion.distance(point.erosion))\n//                + square(this.depth.distance(point.depth))\n//                + square(this.weirdness.distance(point.weirdness))\n//                + square(this.offset - point.offset)\n//    }\n\n\n        //    public space() {\n//        return [this.temperature, this.humidity, this.continentalness, this.erosion, this.depth, this.weirdness, new Param(this.offset, this.offset)]\n//    }\n        public Param[] space() {\n            return new Param[]{this.temperature(), this.humidity(), this.continentalness(), this.erosion(), this.depth(), this.weirdness(), new Param(this.offset(), this.offset())};\n        }\n\n        //    public static fromJson(obj: unknown) {\n//\t\t\tconst root = Json.readObject(obj) ?? {}\n//        return new ParamPoint(\n//                Param.fromJson(root.temperature),\n//                Param.fromJson(root.humidity),\n//                Param.fromJson(root.continentalness),\n//                Param.fromJson(root.erosion),\n//                Param.fromJson(root.depth),\n//                Param.fromJson(root.weirdness),\n//                Json.readInt(root.offset) ?? 0,\n//\t\t\t)\n//    }\n        public static ParamPoint fromJson(Object obj) {\n            if (obj instanceof JsonElement json) {\n                if (json.isJsonObject()) {\n                    JsonObject root = json.getAsJsonObject();\n                    return new ParamPoint(\n                            Param.fromJson(root.get(\"temperature\")),\n                            Param.fromJson(root.get(\"humidity\")),\n                            Param.fromJson(root.get(\"continentalness\")),\n                            Param.fromJson(root.get(\"erosion\")),\n                            Param.fromJson(root.get(\"depth\")),\n                            Param.fromJson(root.get(\"weirdness\")),\n                            root.get(\"offset\").getAsInt()\n                    );\n                }\n            }\n            throw new IllegalArgumentException(\"Cannot convert \" + obj + \" to ParamPoint\");\n        }\n    }\n\n\n    //\texport class TargetPoint {\n//    constructor(\n//            public readonly temperature: number,\n//            public readonly humidity: number,\n//            public readonly continentalness: number,\n//            public readonly erosion: number,\n//            public readonly depth: number,\n//            public readonly weirdness: number,\n//            ) {}\n    public record TargetPoint(double temperature, double humidity, double continentalness, double erosion, double depth,\n                              double weirdness) {\n\n\n        //    get offset() {\n//        return 0\n//    }\n        public double offset() {\n            return 0;\n        }\n\n        //    public toArray() {\n//        return [this.temperature, this.humidity, this.continentalness, this.erosion, this.depth, this.weirdness, this.offset]\n//    }\n        public double[] toArray() {\n            return new double[]{this.temperature(), this.humidity(), this.continentalness(), this.erosion(), this.depth(), this.weirdness(), this.offset()};\n        }\n    }\n\n//export class Parameters<T> {\n//    private readonly index: RTree<T>\n//\n//    constructor(public readonly things: [ParamPoint, () => T][]) {\n//        this.index = new RTree(things)\n//    }\n//\n//    public find(target: TargetPoint) {\n//        return this.index.search(target, (node, values) => node.distance(values))\n//    }\n//}\n\n    public static class Parameters<T> {\n        private final RTree<T> index;\n\n        public Parameters(Map<ParamPoint, Supplier<T>> things) {\n            this.index = new RTree<>(things);\n        }\n\n        public T find(TargetPoint target) {\n            return this.index.search(target, RNode::distance);\n        }\n    }\n\n    //\texport class Sampler {\n//    constructor(\n//            public readonly temperature: DensityFunction,\n//            public readonly humidity: DensityFunction,\n//            public readonly continentalness: DensityFunction,\n//            public readonly erosion: DensityFunction,\n//            public readonly depth: DensityFunction,\n//            public readonly weirdness: DensityFunction,\n//            ) {}\n//\n//    public static fromRouter(router: NoiseRouter) {\n//        return new Climate.Sampler(router.temperature, router.vegetation, router.continents, router.erosion, router.depth, router.ridges)\n//    }\n//\n//    sample(x: number, y: number, z: number) {\n//\t\t\tconst context = DensityFunction.context(x << 2, y << 2, z << 2)\n//        return Climate.target(this.temperature.compute(context), this.humidity.compute(context), this.continentalness.compute(context), this.erosion.compute(context), this.depth.compute(context), this.weirdness.compute(context))\n//    }\n//}\n    public record Sampler(DensityFunction temperature, DensityFunction humidity, DensityFunction continentalness,\n                          DensityFunction erosion, DensityFunction depth, DensityFunction weirdness) {\n        public static Sampler fromRouter(NoiseSettings.NoiseRouter router) {\n            return new Sampler(router.temperature(), router.vegetation(), router.continents(), router.erosion(), router.depth(), router.ridges());\n        }\n\n        public TargetPoint sample(int x, int y, int z) {\n            DensityFunction.Context context = DensityFunction.context(x << 2, y << 2, z << 2);\n            return Climate.target(this.temperature().compute(context), this.humidity().compute(context), this.continentalness().compute(context), this.erosion().compute(context), this.depth().compute(context), this.weirdness().compute(context));\n        }\n    }\n\n    //    type DistanceMetric<T> = (node: RNode<T>, values: number[]) => number\n    interface DistanceMetric<T> {\n        double distance(RNode<T> node, double[] values);\n    }\n\n//export class RTree<T> {\n\n    public static class RTree<T> {\n\n//    public static readonly CHILDREN_PER_NODE = 10\n//    private readonly root: RNode<T>\n\n        private static final int CHILDREN_PER_NODE = 10;\n        private final RNode<T> root;\n//\n//    constructor(points: [ParamPoint, () => T][]) {\n//        if (points.length === 0) {\n//            throw new Error('At least one point is required to build search tree')\n//        }\n//        this.root = RTree.build(points.map(([point, thing]) => new RLeaf(point, thing)))\n//    }\n\n        public RTree(Map<ParamPoint, Supplier<T>> points) {\n            if (points.isEmpty()) {\n                throw new IllegalArgumentException(\"At least one point is required to build search tree\");\n            }\n            var pointList = points.entrySet()\n                    .stream()\n                    .map(entry -> new RLeaf<>(entry.getKey(),\n                            entry.getValue()))\n                    .map(leaf -> (RNode<T>) leaf)\n                    .toList();\n            this.root = RTree.build(pointList);\n        }\n\n//\n//\t\tprivate static build<T>(nodes: RNode<T>[]): RNode<T> {\n//        if (nodes.length === 1) {\n//            return nodes[0]\n//        }\n//        if (nodes.length <= RTree.CHILDREN_PER_NODE) {\n//\t\t\t\tconst sortedNodes = nodes\n//                    .map(node => {\n//                    let key = 0.0\n//            for (let i = 0; i < PARAMETER_SPACE; i += 1) {\n//\t\t\t\t\t\t\tconst param = node.space[i]\n//                key += Math.abs((param.min + param.max) / 2.0)\n//            }\n//            return { key, node }\n//\t\t\t\t\t})\n//\t\t\t\t\t.sort((a, b) => a.key - b.key)\n//\t\t\t\t\t.map(({ node }) => node)\n//            return new RSubTree(sortedNodes)\n//        }\n//        let f = Infinity\n//        let n3 = -1\n//        let result: RSubTree<T>[] = []\n//        for (let n2 = 0; n2 < PARAMETER_SPACE; ++n2) {\n//            nodes = RTree.sort(nodes, n2, false)\n//            result = RTree.bucketize(nodes)\n//            let f2 = 0.0\n//            for (const subTree2 of result) {\n//                f2 += RTree.area(subTree2.space)\n//            }\n//            if (!(f > f2)) continue\n//            f = f2\n//                    n3 = n2\n//        }\n//        nodes = RTree.sort(nodes, n3, false)\n//        result = RTree.bucketize(nodes)\n//        result = RTree.sort(result, n3, true)\n//        return new RSubTree(result.map(subTree => RTree.build(subTree.children)))\n//    }\n\n        private static <T> RNode<T> build(List<RNode<T>> nodes) {\n            if (nodes.size() == 1) {\n                return nodes.get(0);\n            }\n            if (nodes.size() <= RTree.CHILDREN_PER_NODE) {\n                List<RNode<T>> sortedNodes = nodes.stream()\n                        .map(node -> {\n                            double key = 0.0;\n                            for (int i = 0; i < PARAMETER_SPACE; i += 1) {\n                                Param param = node.space[i];\n                                key += Math.abs((param.min() + param.max()) / 2.0);\n                            }\n                            return Map.entry(key, node);\n                        })\n                        .sorted(Comparator.comparingDouble(Map.Entry::getKey))\n                        .map(Map.Entry::getValue)\n                        .collect(Collectors.toList());\n                return new RSubTree<>(sortedNodes);\n            }\n            double f = Double.POSITIVE_INFINITY;\n            int n3 = -1;\n            List<RSubTree<T>> result = new ArrayList<>();\n            for (int n2 = 0; n2 < PARAMETER_SPACE; ++n2) {\n                nodes = RTree.sort(nodes, n2, false);\n                result = RTree.bucketize(nodes);\n                double f2 = 0.0;\n                for (RSubTree<T> subTree2 : result) {\n                    f2 += RTree.area(subTree2.space());\n                }\n                if (!(f > f2)) continue;\n                f = f2;\n                n3 = n2;\n            }\n            nodes = RTree.sort(nodes, n3, false);\n            result = RTree.bucketize(nodes);\n            result = RTree.sort(result, n3, true);\n            return new RSubTree<>(result.stream().map(subTree -> RTree.build(subTree.children)).collect(Collectors.toList()));\n        }\n\n//\n//\t\tprivate static sort<N extends RNode<unknown>>(nodes: N[], i: number, abs: boolean) {\n//        return nodes\n//                .map(node => {\n//\t\t\t\t\tconst param = node.space[i]\n//\t\t\t\t\tconst f = (param.min + param.max) / 2\n//\t\t\t\t\tconst key = abs ? Math.abs(f) : f\n//        return { key, node }\n//\t\t\t\t})\n//\t\t\t\t.sort((a, b) => a.key - b.key)\n//\t\t\t\t.map(({ node }) => node)\n//    }\n\n        private static <N extends RNode<?>> List<N> sort(List<N> nodes, int i, boolean abs) {\n            return nodes.stream()\n                    .map(node -> {\n                        Param param = node.space().get(i);\n                        double f = (param.min() + param.max()) / 2;\n                        double key = abs ? Math.abs(f) : f;\n                        return Map.entry(key, node);\n                    })\n                    .sorted(Comparator.comparingDouble(Map.Entry::getKey))\n                    .map(Map.Entry::getValue)\n                    .collect(Collectors.toList());\n        }\n\n//\n//\t\tprivate static bucketize<T>(nodes: RNode<T>[]) {\n//\t\t\tconst arrayList: RSubTree<T>[] = []\n//        let arrayList2: RNode<T>[] = []\n//\t\t\tconst n = Math.pow(10.0, Math.floor(Math.log(nodes.length - 0.01) / Math.log(10.0)))\n//        for (const node of nodes) {\n//            arrayList2.push(node)\n//            if (arrayList2.length < n) continue\n//            arrayList.push(new RSubTree(arrayList2))\n//            arrayList2 = []\n//        }\n//        if (arrayList2.length !== 0) {\n//            arrayList.push(new RSubTree(arrayList2))\n//        }\n//        return arrayList\n//    }\n\n        private static <T> List<RSubTree<T>> bucketize(List<RNode<T>> nodes) {\n            List<RSubTree<T>> arrayList = new ArrayList<>();\n            List<RNode<T>> arrayList2 = new ArrayList<>();\n            double n = Math.pow(10.0, Math.floor(Math.log(nodes.size() - 0.01) / Math.log(10.0)));\n            for (RNode<T> node : nodes) {\n                arrayList2.add(node);\n                if (arrayList2.size() < n) continue;\n                arrayList.add(new RSubTree<>(arrayList2));\n                arrayList2 = new ArrayList<>();\n            }\n            if (!arrayList2.isEmpty()) {\n                arrayList.add(new RSubTree<>(arrayList2));\n            }\n            return arrayList;\n        }\n\n//\n//    private static area(params: Param[]) {\n//        let f = 0.0\n//        for (const param of params) {\n//            f += Math.abs(param.max - param.min)\n//        }\n//        return f\n//    }\n\n        private static double area(Collection<Param> params) {\n            double f = 0.0;\n            for (Param param : params) {\n                f += Math.abs(param.max() - param.min());\n            }\n            return f;\n        }\n\n//\n//    public search(target: TargetPoint, distance: DistanceMetric<T>) {\n//\t\t\tconst leaf = this.root.search(target.toArray(), distance)\n//        return leaf.thing()\n//    }\n\n        public T search(TargetPoint target, DistanceMetric<T> distance) {\n            RLeaf<T> leaf = this.root.search(target.toArray(), distance);\n            return leaf.thing.get();\n        }\n//}\n    }\n\n    //\texport abstract class RNode<T> {\n//    constructor(public readonly space: Param[]) {}\n//\n//    public abstract search(values: number[], distance: DistanceMetric<T>): RLeaf<T>\n//\n//    public distance(values: number[]) {\n//        let result = 0\n//        for (let i = 0; i < PARAMETER_SPACE; i += 1) {\n//            result += square(this.space[i].distance(values[i]))\n//        }\n//        return result\n//    }\n//}\n    static abstract class RNode<T> {\n        protected final Param[] space;\n\n        public RNode(Param[] space) {\n            this.space = space;\n        }\n\n        public abstract RLeaf<T> search(double[] values, DistanceMetric<T> distance);\n\n        public double distance(double[] values) {\n            double result = 0;\n            for (int i = 0; i < PARAMETER_SPACE; i += 1) {\n                result += Util.square(this.space[i].distance(Param.fromJson(values[i])));\n            }\n            return result;\n        }\n\n        public List<Param> space() {\n            return Arrays.asList(this.space);\n        }\n    }\n\n    //export class RSubTree<T> extends RNode<T> {\n//    constructor(public readonly children: RNode<T>[]) {\n//        super(RSubTree.buildSpace(children))\n//    }\n//\n//\t\tprivate static buildSpace<T>(nodes: RNode<T>[]): Param[] {\n//        let space = [...Array(PARAMETER_SPACE)].map(() => new Param(Infinity, -Infinity))\n//        for (const node of nodes) {\n//            space = [...Array(PARAMETER_SPACE)].map((_, i) => space[i].union(node.space[i]))\n//        }\n//        return space\n//    }\n//\n//    search(values: number[], distance: DistanceMetric<T>): RLeaf<T> {\n//        let dist = Infinity\n//        let leaf: RLeaf<T> | null = null\n//        for (const node of this.children) {\n//\t\t\t\tconst d1 = distance(node, values)\n//            if (dist <= d1) continue\n//\t\t\t\tconst leaf2 = node.search(values, distance)\n//\t\t\t\tconst d2 = node == leaf2 ? d1 : distance(leaf2, values)\n//            if (dist <= d2) continue\n//            dist = d2\n//                    leaf = leaf2\n//        }\n//        return leaf!\n//    }\n//}\n    static class RSubTree<T> extends RNode<T> {\n        private final List<RNode<T>> children;\n\n        public RSubTree(List<RNode<T>> children) {\n            super(RSubTree.buildSpace(children));\n            this.children = children;\n        }\n\n        private static <T> Param[] buildSpace(List<RNode<T>> nodes) {\n            Param[] space = new Param[PARAMETER_SPACE];\n            for (int i = 0; i < PARAMETER_SPACE; i += 1) {\n                space[i] = new Param(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);\n            }\n            for (RNode<T> node : nodes) {\n                for (int i = 0; i < PARAMETER_SPACE; i += 1) {\n                    space[i] = space[i].union(node.space().get(i));\n                }\n            }\n            return space;\n        }\n\n        @Override\n        public RLeaf<T> search(double[] values, DistanceMetric<T> distance) {\n            double dist = Double.POSITIVE_INFINITY;\n            RLeaf<T> leaf = null;\n            for (RNode<T> node : this.children) {\n                double d1 = distance.distance(node, values);\n                if (dist <= d1) continue;\n                RLeaf<T> leaf2 = node.search(values, distance);\n                double d2 = node == leaf2 ? d1 : distance.distance(leaf2, values);\n                if (dist <= d2) continue;\n                dist = d2;\n                leaf = leaf2;\n            }\n            return leaf;\n        }\n    }\n\n//\texport class RLeaf<T> extends RNode<T> {\n//    constructor(point: ParamPoint, public readonly thing: () => T) {\n//        super(point.space())\n//    }\n//\n//    search() {\n//        return this\n//    }\n//}\n\n    static class RLeaf<T> extends RNode<T> {\n        private final Supplier<T> thing;\n\n        public RLeaf(ParamPoint point, Supplier<T> thing) {\n            super(point.space());\n            this.thing = thing;\n        }\n\n        @Override\n        public RLeaf<T> search(double[] values, DistanceMetric<T> distance) {\n            return this;\n        }\n    }\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/math/CubicSpline.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.math;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\npublic interface CubicSpline extends NumberFunction<DensityFunction.Context> {\n\n    static CubicSpline fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMap(reader, token -> switch (token) {\n            case NUMBER -> json -> new Constant(json.nextDouble());\n            case BEGIN_OBJECT -> json -> DatapackLoader.moshi(MultiPoint.class).apply(json);\n            default -> null;\n        });\n    }\n\n\n    double min();\n\n    double max();\n    record Constant(double value) implements CubicSpline {\n        @Override\n        public double min() {\n            return value;\n        }\n\n        @Override\n        public double max() {\n            return value;\n        }\n\n        @Override\n        public double compute(DensityFunction.Context context) {\n            return value;\n        }\n    }\n\n    record MultiPoint(DensityFunction coordinate, List<Point> points) implements CubicSpline {\n\n        // TODO: Remove this static map, and instead use lazily loaded class fields\n        private static final Map<MultiPoint, CachedMinMax> cache = Collections.synchronizedMap(new WeakHashMap<>());\n\n        private record CachedMinMax(double min, double max) {\n        }\n\n        @Override\n        public double compute(DensityFunction.Context coordinate) {\n            double c = this.coordinate.compute(coordinate);\n            int pointsLength = this.points.size();\n            int i = Util.binarySearch(0, pointsLength, n -> c < this.points.get(n).location() - 1);\n            int n = pointsLength - 1;\n\n            if (i < 0) {\n                Point point = this.points.get(0);\n                return point.value().compute(coordinate) + point.derivative() * (c - point.location());\n            }\n            if (i == n) {\n                Point point = this.points.get(n);\n                return point.value().compute(coordinate) + point.derivative() * (c - point.location());\n            }\n            if (i > n) {\n                throw new IllegalStateException(\"i > n\");\n            }\n\n            Point point0 = this.points.get(i);\n            Point point1 = this.points.get(i + 1);\n            double loc0 = point0.location();\n            double loc1 = point1.location();\n            double der0 = point0.derivative();\n            double der1 = point1.derivative();\n            double f = (c - loc0) / (loc1 - loc0);\n\n            double val0 = point0.value().compute(coordinate);\n            double val1 = point1.value().compute(coordinate);\n\n            double f8 = der0 * (loc1 - loc0) - (val1 - val0);\n            double f9 = -der1 * (loc1 - loc0) + (val1 - val0);\n            return Util.lerp(f, val0, val1) + f * (1.0 - f) * Util.lerp(f, f8, f9);\n        }\n\n        private CachedMinMax minMax() {\n            if (cache.containsKey(this)) {\n                return cache.get(this);\n            }\n            int pointsLength = this.points.size();\n            int lastIdx = pointsLength - 1;\n            double splineMin = Double.POSITIVE_INFINITY;\n            double splineMax = Double.NEGATIVE_INFINITY;\n            double coordinateMin = coordinate.minValue();\n            double coordinateMax = coordinate.maxValue();\n            Point first = this.points.get(0);\n\n            if (coordinateMin < first.location()) {\n                double minExtend = MultiPoint.linearExtend(coordinateMin, first, first.value().min());\n                double maxExtend = MultiPoint.linearExtend(coordinateMin, first, first.value().max());\n                splineMin = Math.min(splineMin, Math.min(minExtend, maxExtend));\n                splineMax = Math.max(splineMax, Math.max(minExtend, maxExtend));\n            }\n            Point last = this.points.get(lastIdx);\n\n            if (coordinateMax > last.location()) {\n                double minExtend = MultiPoint.linearExtend(coordinateMax, last, last.value().min());\n                double maxExtend = MultiPoint.linearExtend(coordinateMax, last, last.value().max());\n                splineMin = Math.min(splineMin, Math.min(minExtend, maxExtend));\n                splineMax = Math.max(splineMax, Math.max(minExtend, maxExtend));\n            }\n\n            for (Point point : points()) {\n                CubicSpline innerSpline = point.value();\n                splineMin = Math.min(splineMin, innerSpline.min());\n                splineMax = Math.max(splineMax, innerSpline.max());\n            }\n\n            for (int i = 0; i < lastIdx; ++i) {\n                Point pointLeft = this.points.get(i);\n                Point pointRight = this.points.get(i + 1);\n                double locationLeft = pointLeft.location();\n                double locationRight = pointRight.location();\n                double locationDelta = locationRight - locationLeft;\n                CubicSpline splineLeft = pointLeft.value();\n                CubicSpline splineRight = pointRight.value();\n                double minLeft = splineLeft.min();\n                double maxLeft = splineLeft.max();\n                double minRight = splineRight.min();\n                double maxRight = splineRight.max();\n                double derivativeLeft = pointLeft.derivative();\n                double derivativeRight = pointRight.derivative();\n                if (derivativeLeft != 0.0 || derivativeRight != 0.0) {\n                    double maxValueDeltaLeft = derivativeLeft * locationDelta;\n                    double maxValueDeltaRight = derivativeRight * locationDelta;\n                    double minValue = Math.min(minLeft, minRight);\n                    double maxValue = Math.max(maxLeft, maxRight);\n                    double minDeltaLeft = maxValueDeltaLeft - maxRight + minLeft;\n                    double maxDeltaLeft = maxValueDeltaLeft - minRight + maxLeft;\n                    double minDeltaRight = -maxValueDeltaRight + minRight - maxLeft;\n                    double maxDeltaRight = -maxValueDeltaRight + maxRight - minLeft;\n                    double minDelta = Math.min(minDeltaLeft, minDeltaRight);\n                    double maxDelta = Math.max(maxDeltaLeft, maxDeltaRight);\n                    splineMin = Math.min(splineMin, minValue + 0.25 * minDelta);\n                    splineMax = Math.max(splineMax, maxValue + 0.25 * maxDelta);\n                }\n            }\n\n            CachedMinMax cachedMinMax = new CachedMinMax(splineMin, splineMax);\n            cache.put(this, cachedMinMax);\n            return cachedMinMax;\n        }\n\n        @Override\n        public double min() {\n            return minMax().min();\n        }\n\n        @Override\n        public double max() {\n            return minMax().max();\n        }\n\n        public record Point(double location, CubicSpline value, double derivative) {\n        }\n\n        private static double linearExtend(double location, Point point, double value) {\n            double derivative = point.derivative();\n            return derivative == 0.0 ? value : value + derivative * (location - point.location());\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/math/NumberFunction.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.math;\n\npublic interface NumberFunction<C> {\n    double compute(C c);\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/math/SplineInterpolator.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.math;\n\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\n\n/**\n * Performs spline interpolation given a set of control points.\n */\npublic class SplineInterpolator {\n\n    private final DoubleList mX;\n    private final DoubleList mY;\n    private final double[] mM;\n\n    private SplineInterpolator(DoubleList x, DoubleList y, double[] m) {\n        mX = x;\n        mY = y;\n        mM = m;\n    }\n\n    /**\n     * Creates a monotone cubic spline from a given set of control points.\n     * <p>\n     * The spline is guaranteed to pass through each control point exactly. Moreover, assuming the control points are\n     * monotonic (Y is non-decreasing or non-increasing) then the interpolated values will also be monotonic.\n     * <p>\n     * This function uses the Fritsch-Carlson method for computing the spline parameters.\n     * <a href=\"http://en.wikipedia.org/wiki/Monotone_cubic_interpolation\">see here</a>\n     *\n     * @param x The X component of the control points, strictly increasing.\n     * @param y The Y component of the control points\n     * @return A monotone cubic spline interpolator.\n     * @throws IllegalArgumentException if the X or Y arrays are null, have different lengths or have fewer than 2 values.\n     */\n    public static SplineInterpolator createMonotoneCubicSpline(DoubleList x, DoubleList y) {\n        if (x == null || y == null || x.size() != y.size() || x.size() < 2) {\n            throw new IllegalArgumentException(\"There must be at least two control \"\n                    + \"points and the arrays must be of equal length.\");\n        }\n\n        final int n = x.size();\n        double[] d = new double[n - 1]; // could optimize this out\n        double[] m = new double[n];\n\n        // Compute slopes of secant lines between successive points.\n        for (int i = 0; i < n - 1; i++) {\n            double h = x.getDouble(i + 1) - x.getDouble(i);\n            if (h <= 0f) {\n                throw new IllegalArgumentException(\"The control points must all \"\n                        + \"have strictly increasing X values.\");\n            }\n            d[i] = (y.getDouble(i + 1) - y.getDouble(i)) / h;\n        }\n\n        // Initialize the tangents as the average of the secants.\n        m[0] = d[0];\n        for (int i = 1; i < n - 1; i++) {\n            m[i] = (d[i - 1] + d[i]) * 0.5f;\n        }\n        m[n - 1] = d[n - 2];\n\n        // Update the tangents to preserve monotonicity.\n        for (int i = 0; i < n - 1; i++) {\n            if (d[i] == 0f) { // successive Y values are equal\n                m[i] = 0f;\n                m[i + 1] = 0f;\n            } else {\n                double a = m[i] / d[i];\n                double b = m[i + 1] / d[i];\n                double h = Math.hypot(a, b);\n                if (h > 3f) {\n                    double t = 3f / h;\n                    m[i] = t * a * d[i];\n                    m[i + 1] = t * b * d[i];\n                }\n            }\n        }\n        return new SplineInterpolator(x, y, m);\n    }\n\n    /**\n     * Interpolates the value of Y = f(X) for given X. Clamps X to the domain of the spline.\n     *\n     * @param x The X value.\n     * @return The interpolated Y = f(X) value.\n     */\n    public double interpolate(double x) {\n        // Handle the boundary cases.\n        final int n = mX.size();\n        if (Double.isNaN(x)) {\n            return x;\n        }\n        if (x <= mX.getDouble(0)) {\n            return mY.getDouble(0);\n        }\n        if (x >= mX.getDouble(n - 1)) {\n            return mY.getDouble(n - 1);\n        }\n\n        // Find the index 'i' of the last point with smaller X.\n        // We know this will be within the spline due to the boundary tests.\n        int i = 0;\n        while (x >= mX.getDouble(i + 1)) {\n            i += 1;\n            if (x == mX.getDouble(i)) {\n                return mY.getDouble(i);\n            }\n        }\n\n        // Perform cubic Hermite spline interpolation.\n        double h = mX.getDouble(i + 1) - mX.getDouble(i);\n        double t = (x - mX.getDouble(i)) / h;\n        return (mY.getDouble(i) * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)\n                + (mY.getDouble(i + 1) * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;\n    }\n\n    // For debugging.\n    @Override\n    public String toString() {\n        StringBuilder str = new StringBuilder();\n        final int n = mX.size();\n        str.append(\"[\");\n        for (int i = 0; i < n; i++) {\n            if (i != 0) {\n                str.append(\", \");\n            }\n            str.append(\"(\").append(mX.getDouble(i));\n            str.append(\", \").append(mY.getDouble(i));\n            str.append(\": \").append(mM[i]).append(\")\");\n        }\n        str.append(\"]\");\n        return str.toString();\n    }\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/BlendedNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\nimport org.jetbrains.annotations.Nullable;\n\npublic class BlendedNoise implements Noise {\n    public final PerlinNoise minLimitNoise;\n    public final PerlinNoise maxLimitNoise;\n    public final PerlinNoise mainNoise;\n    private final double xzMultiplier;\n    private final double yMultiplier;\n    public final double maxValue;\n\n    // constructor fields\n    private final double xzFactor;\n    private final double yFactor;\n    private final double smearScaleMultiplier;\n\n    public BlendedNoise(WorldgenRandom random, double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier) {\n        this.xzFactor = xzFactor;\n        this.yFactor = yFactor;\n        this.smearScaleMultiplier = smearScaleMultiplier;\n        this.minLimitNoise = new PerlinNoise(random, -15, new double[]{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0});\n        this.maxLimitNoise = new PerlinNoise(random, -15, new double[]{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0});\n        this.mainNoise = new PerlinNoise(random, -7, new double[]{1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0});\n        this.xzMultiplier = 684.412 * xzScale;\n        this.yMultiplier = 684.412 * yScale;\n        this.maxValue = this.minLimitNoise.edgeValue(yScale + 2); //TODO\n    }\n\n    @Override\n    public double sample(double x, double y, double z) {\n        double scaledX = x * this.xzMultiplier;\n        double scaledY = y * this.yMultiplier;\n        double scaledZ = z * this.xzMultiplier;\n\n        double factoredX = scaledX / this.xzFactor;\n        double factoredY = scaledY / this.yFactor;\n        double factoredZ = scaledZ / this.xzFactor;\n\n        double smear = this.yMultiplier * this.smearScaleMultiplier;\n        double factoredSmear = smear / this.yFactor;\n\n        @Nullable ImprovedNoise noise;\n        double value = 0;\n        double factor = 1;\n        for (int i = 0; i < 8; i += 1) {\n            noise = this.mainNoise.getOctaveNoise(i);\n            if (noise != null) {\n                double xx = PerlinNoise.wrap(factoredX * factor);\n                double yy = PerlinNoise.wrap(factoredY * factor);\n                double zz = PerlinNoise.wrap(factoredZ * factor);\n                value += noise.sample(xx, yy, zz, factoredSmear * factor, factoredY * factor) / factor;\n            }\n            factor /= 2;\n        }\n\n        value = (value / 10 + 1) / 2;\n        factor = 1;\n        double min = 0;\n        double max = 0;\n        for (int i = 0; i < 16; i += 1) {\n            double xx = PerlinNoise.wrap(scaledX * factor);\n            double yy = PerlinNoise.wrap(scaledY * factor);\n            double zz = PerlinNoise.wrap(scaledZ * factor);\n            double smearsmear = smear * factor;\n            if (value < 1 && (noise = this.minLimitNoise.getOctaveNoise(i)) != null) {\n                min += noise.sample(xx, yy, zz, smearsmear, scaledY * factor) / factor;\n            }\n            if (value > 0 && (noise = this.maxLimitNoise.getOctaveNoise(i)) != null) {\n                max += noise.sample(xx, yy, zz, smearsmear, scaledY * factor) / factor;\n            }\n            factor /= 2;\n        }\n\n        return Util.clampedLerp(min / 512, max / 512, value) / 128;\n    }\n\n    @Override\n    public double minValue() {\n        return 0;\n    }\n\n    @Override\n    public double maxValue() {\n        return 1;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/ImprovedNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\npublic class ImprovedNoise implements Noise {\n\n    public final int[] p;\n    public final double xo;\n    public final double yo;\n    public final double zo;\n\n    public ImprovedNoise(WorldgenRandom random) {\n        this.xo = random.nextDouble() * 256;\n        this.yo = random.nextDouble() * 256;\n        this.zo = random.nextDouble() * 256;\n        this.p = new int[256];\n\n        for (int i = 0; i < 256; i += 1) {\n            this.p[i] = i > 127 ? i - 256 : i;\n        }\n        for (int i = 0; i < 256; i += 1) {\n            int j = random.nextInt(256 - i);\n            int b = this.p[i];\n            this.p[i] = this.p[i + j];\n            this.p[i + j] = b;\n        }\n    }\n\n    public double sample(double x, double y, double z) {\n        return this.sample(x, y, z, 0, 0);\n    }\n\n    @Override\n    public double minValue() {\n        return -1;\n    }\n\n    @Override\n    public double maxValue() {\n        return 1;\n    }\n\n    public double sample(double x, double y, double z, double yScale, double yLimit) {\n        double x2 = x + this.xo;\n        double y2 = y + this.yo;\n        double z2 = z + this.zo;\n        int x3 = (int) Math.floor(x2);\n        int y3 = (int) Math.floor(y2);\n        int z3 = (int) Math.floor(z2);\n        double x4 = x2 - x3;\n        double y4 = y2 - y3;\n        double z4 = z2 - z3;\n\n        double y6 = 0;\n        if (yScale != 0) {\n            double t = yLimit >= 0 && yLimit < y4 ? yLimit : y4;\n            y6 = Math.floor(t / yScale + 1e-7) * yScale;\n        }\n\n        return this.sampleAndLerp(x3, y3, z3, x4, y4 - y6, z4, y4);\n    }\n\n    private double sampleAndLerp(int a, int b, int c, double d, double e, double f, double g) {\n        int h = this.P(a);\n        int i = this.P(a + 1);\n        int j = this.P(h + b);\n        int k = this.P(h + b + 1);\n        int l = this.P(i + b);\n        int m = this.P(i + b + 1);\n\n        // import { lerp3, smoothstep } from '../Util.js'\n\n        double n = gradDot(this.P(j + c), d, e, f);\n        double o = gradDot(this.P(l + c), d - 1.0, e, f);\n        double p = gradDot(this.P(k + c), d, e - 1.0, f);\n        double q = gradDot(this.P(m + c), d - 1.0, e - 1.0, f);\n        double r = gradDot(this.P(j + c + 1), d, e, f - 1.0);\n        double s = gradDot(this.P(l + c + 1), d - 1.0, e, f - 1.0);\n        double t = gradDot(this.P(k + c + 1), d, e - 1.0, f - 1.0);\n        double u = gradDot(this.P(m + c + 1), d - 1.0, e - 1.0, f - 1.0);\n\n        double v = Util.smoothstep(d);\n        double w = Util.smoothstep(g);\n        double x = Util.smoothstep(f);\n\n        return Util.lerp3(v, w, x, n, o, p, q, r, s, t, u);\n    }\n\n    public static double gradDot(int a, double b, double c, double d) {\n        var grad = SimplexNoise.GRADIENT[a & 15];\n        return grad[0] * b + grad[1] * c + grad[2] * d;\n    }\n\n    private int P(int i) {\n        return this.p[i & 0xFF] & 0xFF;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/LazyLoadedNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.DatapackUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nclass LazyLoadedNoise implements Noise {\n    private @Nullable Noise noise = null;\n\n    public LazyLoadedNoise(String id, DatapackLoader.LoadingContext context) {\n        context.whenFinished(finish -> noise = DatapackUtils.findNoise(finish.datapack(), id).orElseThrow());\n    }\n\n    private @NotNull Noise noise() {\n        if (noise == null) {\n            throw new IllegalStateException(\"Noise not loaded yet\");\n        }\n        return noise;\n    }\n\n    @Override\n    public double sample(double x, double y, double z) {\n        return noise().sample(x, y, z);\n    }\n\n    @Override\n    public double minValue() {\n        return noise().minValue();\n    }\n\n    @Override\n    public double maxValue() {\n        return noise().maxValue();\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/Noise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport com.squareup.moshi.JsonReader;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.json.JsonUtils;\n\nimport java.io.IOException;\n\npublic interface Noise {\n    Noise ZERO = new NoiseZero();\n\n    double sample(double x, double y, double z);\n\n    double minValue();\n    double maxValue();\n\n    static Noise fromJson(JsonReader reader) throws IOException {\n        return JsonUtils.typeMap(reader, token -> switch (token) {\n            case STRING -> json -> { // string means use a json-defined noise. This will need to be lazily loaded.\n                String id = json.nextString();\n                return new LazyLoadedNoise(id, DatapackLoader.loading());\n            };\n            case BEGIN_OBJECT -> json -> {\n                NormalNoise.Config params = DatapackLoader.moshi(NormalNoise.Config.class).apply(json);\n                if (DatapackLoader.loading().isStatic()) {\n                    // if we're static, to match vanilla we return zero.\n                    return Noise.ZERO;\n                }\n                return new NormalNoise(DatapackLoader.loading().random(), params);\n            };\n            default -> null;\n        });\n    }\n}\n\n\nrecord NoiseZero() implements Noise {\n\n    @Override\n    public double sample(double x, double y, double z) {\n        return 0;\n    }\n\n    @Override\n    public double minValue() {\n        return 0;\n    }\n\n    @Override\n    public double maxValue() {\n        return 0;\n    }\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/NormalNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\n\npublic class NormalNoise implements Noise {\n\n    public record Config(double firstOctave, DoubleList amplitudes) {\n    }\n\n    private static final double INPUT_FACTOR = 1.0181268882175227;\n\n    public final double valueFactor;\n    public final PerlinNoise first;\n    public final PerlinNoise second;\n    public final double maxValue;\n\n    public NormalNoise(WorldgenRandom random, Config config) {\n        double firstOctave = config.firstOctave();\n        DoubleList amplitudes = config.amplitudes();\n        this.first = new PerlinNoise(random, firstOctave, amplitudes);\n        this.second = new PerlinNoise(random, firstOctave, amplitudes);\n\n        double min = Double.MAX_VALUE;\n        double max = Double.MIN_VALUE;\n        for (int i = 0; i < amplitudes.size(); i += 1) {\n            if (amplitudes.getDouble(i) != 0) {\n                min = Math.min(min, i);\n                max = Math.max(max, i);\n            }\n        }\n\n        double expectedDeviation = 0.1 * (1 + 1 / (max - min + 1));\n        this.valueFactor = (1.0 / 6.0) / expectedDeviation;\n        this.maxValue = (this.first.maxValue + this.second.maxValue) * this.valueFactor;\n    }\n\n    @Override\n    public double sample(double x, double y, double z) {\n        double x2 = x * NormalNoise.INPUT_FACTOR;\n        double y2 = y * NormalNoise.INPUT_FACTOR;\n        double z2 = z * NormalNoise.INPUT_FACTOR;\n        return (this.first.sample(x, y, z) + this.second.sample(x2, y2, z2)) * this.valueFactor;\n    }\n\n    @Override\n    public double minValue() {\n        return 0;\n    }\n\n    @Override\n    public double maxValue() {\n        return this.maxValue;\n    }\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/PerlinNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.random.XoroshiroRandom;\n\npublic class PerlinNoise implements Noise {\n    public final ImprovedNoise[] noiseLevels;\n    public final double[] amplitudes;\n    public final double lowestFreqInputFactor;\n    public final double lowestFreqValueFactor;\n    public final double maxValue;\n\n    public PerlinNoise(WorldgenRandom random, double firstOctave, double[] amplitudes) {\n        this(random, firstOctave, DoubleList.of(amplitudes));\n    }\n\n    public PerlinNoise(WorldgenRandom random, double firstOctave, DoubleList amplitudes) {\n        this.amplitudes = amplitudes.toDoubleArray();\n        this.noiseLevels = new ImprovedNoise[amplitudes.size()];\n\n        if (random instanceof XoroshiroRandom) {\n            WorldgenRandom.Positional forkedRandom = random.forkPositional();\n\n            for (int i = 0; i < amplitudes.size(); i++) {\n                if (amplitudes.getDouble(i) != 0.0) {\n                    double octave = firstOctave + i;\n                    this.noiseLevels[i] = new ImprovedNoise(forkedRandom.fromHashOf(\"octave_\" + octave));\n                }\n            }\n        } else {\n            if (1 - firstOctave < amplitudes.size()) {\n                throw new RuntimeException(\"Positive octaves are not allowed when using LegacyRandom\");\n            }\n\n\n            for (int i = (int) -firstOctave; i >= 0; i -= 1) {\n                if (i < amplitudes.size() && amplitudes.getDouble(i) != 0) {\n                    this.noiseLevels[i] = new ImprovedNoise(random);\n                } else {\n                    random.consumeInt(262);\n                }\n            }\n        }\n        this.lowestFreqInputFactor = Math.pow(2, firstOctave);\n        this.lowestFreqValueFactor = Math.pow(2, (amplitudes.size() - 1)) / (Math.pow(2, amplitudes.size()) - 1);\n        this.maxValue = this.edgeValue(2);\n    }\n\n    public double sample(double x, double y, double z) {\n        return sample(x, y, z, 0, 0, false);\n    }\n\n    @Override\n    public double minValue() {\n        return 0;\n    }\n\n    @Override\n    public double maxValue() {\n        return this.maxValue;\n    }\n\n    public double sample(double x, double y, double z, double yScale, double yLimit, boolean fixY) {\n        var value = 0.0;\n        double inputF = this.lowestFreqInputFactor;\n        double valueF = this.lowestFreqValueFactor;\n        for (var i = 0; i < this.noiseLevels.length; i += 1) {\n            ImprovedNoise noise = this.noiseLevels[i];\n            if (noise != null) {\n                value += this.amplitudes[i] * valueF * noise.sample(\n                        PerlinNoise.wrap(x * inputF),\n                        fixY ? -noise.yo : PerlinNoise.wrap(y * inputF),\n                        PerlinNoise.wrap(z * inputF),\n                        yScale * inputF,\n                        yLimit * inputF);\n            }\n            inputF *= 2;\n            valueF /= 2;\n        }\n        return value;\n    }\n\n    public ImprovedNoise getOctaveNoise(int i) {\n        return this.noiseLevels[this.noiseLevels.length - 1 - i];\n    }\n\n    public double edgeValue(double x) {\n        var value = 0;\n        var valueF = this.lowestFreqValueFactor;\n        for (int i = 0; i < this.noiseLevels.length; i += 1) {\n            if (this.noiseLevels[i] != null) {\n                value += this.amplitudes[i] * x * valueF;\n            }\n            valueF /= 2;\n        }\n        return value;\n    }\n\n    public static double wrap(double value) {\n        return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7;\n    }\n}"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/noise/SimplexNoise.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.noise;\n\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\n\npublic class SimplexNoise implements Noise {\n\n    static final int[][] GRADIENT = new int[][]{{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1},\n            {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1},\n            {-1, 1, 0}, {0, -1, -1}};\n    private static final double F2 = 0.5 * (Math.sqrt(3.0) - 1.0);\n    private static final double G2 = (3.0 - Math.sqrt(3.0)) / 6.0;\n\n    public final int[] p = new int[512];\n    public final double xo;\n    public final double yo;\n    public final double zo;\n\n    public SimplexNoise(WorldgenRandom random) {\n        this.xo = random.nextDouble() * 256.0;\n        this.yo = random.nextDouble() * 256.0;\n        this.zo = random.nextDouble() * 256.0;\n\n        {\n            int i = 0;\n            while (i < 256) {\n                this.p[i] = i++;\n            }\n        }\n        for (int i = 0; i < 256; ++i) {\n            int j = random.nextInt(256 - i);\n            int b = this.p[i];\n            this.p[i] = this.p[i + j];\n            this.p[i + j] = b;\n        }\n    }\n\n    public double sample2D(double x, double z) {\n        double offset = (x + z) * F2;\n        int offsetA = floor(x + offset);\n        int offsetB = floor(z + offset);\n        double diff = (double) (offsetA + offsetB) * G2;\n        double diffA = (double) offsetA - diff;\n        double diffB = (double) offsetB - diff;\n        double adjustedA = x - diffA;\n        double adjustedB = z - diffB;\n        byte aIsLarger;\n        byte bIsLarger;\n        if (adjustedA > adjustedB) {\n            aIsLarger = 1;\n            bIsLarger = 0;\n        } else {\n            aIsLarger = 0;\n            bIsLarger = 1;\n        }\n\n        double a1 = adjustedA - (double) aIsLarger + G2;\n        double b1 = adjustedB - (double) bIsLarger + G2;\n        double a2 = adjustedA - 1.0 + 2.0 * G2;\n        double b2 = adjustedB - 1.0 + 2.0 * G2;\n        int a3 = offsetA & 255;\n        int b3 = offsetB & 255;\n        int x1 = this.get(a3 + this.get(b3)) % 12;\n        int y1 = this.get(a3 + aIsLarger + this.get(b3 + bIsLarger)) % 12;\n        int z1 = this.get(a3 + 1 + this.get(b3 + 1)) % 12;\n        double x2 = this.getCornerNoise3D(x1, adjustedA, adjustedB, 0.0, 0.5);\n        double y2 = this.getCornerNoise3D(y1, a1, b1, 0.0, 0.5);\n        double z2 = this.getCornerNoise3D(z1, a2, b2, 0.0, 0.5);\n        return 70.0 * (x2 + y2 + z2);\n    }\n\n    private static int floor(double x) {\n        int intX = (int) x;\n        return x < (double) intX ? intX - 1 : intX;\n    }\n\n    public double sample(double x, double y, double z) {\n        double offset = (x + y + z) * 0.3333333333333333;\n        int x1 = floor(x + offset);\n        int y1 = floor(y + offset);\n        int z1 = floor(z + offset);\n        double diff = (double) (x1 + y1 + z1) * 0.16666666666666666;\n        double x2 = (double) x1 - diff;\n        double y2 = (double) y1 - diff;\n        double z2 = (double) z1 - diff;\n        double x3 = x - x2;\n        double y3 = y - y2;\n        double z3 = z - z2;\n        byte x4;\n        byte y4;\n        byte z4;\n        byte x5;\n        byte y5;\n        byte z5;\n        if (x3 >= y3) {\n            if (y3 >= z3) {\n                x4 = 1;\n                y4 = 0;\n                z4 = 0;\n                x5 = 1;\n                y5 = 1;\n                z5 = 0;\n            } else if (x3 >= z3) {\n                x4 = 1;\n                y4 = 0;\n                z4 = 0;\n                x5 = 1;\n                y5 = 0;\n                z5 = 1;\n            } else {\n                x4 = 0;\n                y4 = 0;\n                z4 = 1;\n                x5 = 1;\n                y5 = 0;\n                z5 = 1;\n            }\n        } else if (y3 < z3) {\n            x4 = 0;\n            y4 = 0;\n            z4 = 1;\n            x5 = 0;\n            y5 = 1;\n            z5 = 1;\n        } else if (x3 < z3) {\n            x4 = 0;\n            y4 = 1;\n            z4 = 0;\n            x5 = 0;\n            y5 = 1;\n            z5 = 1;\n        } else {\n            x4 = 0;\n            y4 = 1;\n            z4 = 0;\n            x5 = 1;\n            y5 = 1;\n            z5 = 0;\n        }\n\n        double x6 = x3 - (double) x4 + 0.16666666666666666;\n        double y6 = y3 - (double) y4 + 0.16666666666666666;\n        double z6 = z3 - (double) z4 + 0.16666666666666666;\n        double x7 = x3 - (double) x5 + 0.3333333333333333;\n        double y7 = y3 - (double) y5 + 0.3333333333333333;\n        double z7 = z3 - (double) z5 + 0.3333333333333333;\n        double x8 = x3 - 1.0 + 0.5;\n        double y8 = y3 - 1.0 + 0.5;\n        double z8 = z3 - 1.0 + 0.5;\n        int x9 = x1 & 255;\n        int y9 = y1 & 255;\n        int z9 = z1 & 255;\n        int a = this.get(x9 + this.get(y9 + this.get(z9))) % 12;\n        int b = this.get(x9 + x4 + this.get(y9 + y4 + this.get(z9 + z4))) % 12;\n        int c = this.get(x9 + x5 + this.get(y9 + y5 + this.get(z9 + z5))) % 12;\n        int d = this.get(x9 + 1 + this.get(y9 + 1 + this.get(z9 + 1))) % 12;\n        double e = this.getCornerNoise3D(a, x3, y3, z3, 0.6);\n        double f = this.getCornerNoise3D(b, x6, y6, z6, 0.6);\n        double g = this.getCornerNoise3D(c, x7, y7, z7, 0.6);\n        double h = this.getCornerNoise3D(d, x8, y8, z8, 0.6);\n        return 32.0 * (e + f + g + h);\n    }\n\n    @Override\n    public double minValue() {\n        return 0;\n    }\n\n    @Override\n    public double maxValue() {\n        return 1;\n    }\n\n    private int get(int i) {\n        return this.p[i & 255];\n    }\n\n    private double getCornerNoise3D(int i, double a, double b, double c, double d) {\n        double e = d - a * a - b * b - c * c;\n        double f;\n        if (e < 0.0) {\n            f = 0.0;\n        } else {\n            e *= e;\n            f = e * e * dot(GRADIENT[i], a, b, c);\n        }\n        return f;\n    }\n\n    protected static double dot(int[] grad, double a, double b, double c) {\n        return (double) grad[0] * a + (double) grad[1] * b + (double) grad[2] * c;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/random/LegacyRandom.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.random;\n\npublic class LegacyRandom implements WorldgenRandom {\n\n    private long seed;\n\n    public LegacyRandom(long seed) {\n        this.seed = (seed ^ 25214903917L) & 281474976710655L;\n    }\n\n    @Override\n    public long nextLong() {\n        int high = this.next(32);\n        int low = this.next(32);\n        return ((long) high << 32) + (long) low;\n    }\n\n    @Override\n    public int nextInt() {\n        return next(32);\n    }\n\n    @Override\n    public int nextInt(int bound) {\n        if (bound <= 0) {\n            throw new IllegalArgumentException(\"Bound must be positive\");\n        }\n\n        if ((bound & bound - 1) == 0) {\n            return (int) ((long) bound * (long) this.next(31) >> 31);\n        }\n\n        int prev;\n        int out;\n        prev = this.next(31);\n        out = prev % bound;\n        while (prev - out + (bound - 1) < 0) {\n            prev = this.next(31);\n            out = prev % bound;\n        }\n        return out;\n    }\n\n    @Override\n    public double nextDouble() {\n        int high = this.next(26);\n        int low = this.next(27);\n        long compose = ((long) high << 27) + (long) low;\n        return (double) compose * 1.1102230246251565E-16;\n    }\n\n    @Override\n    public WorldgenRandom fork() {\n        return new LegacyRandom(this.nextLong());\n    }\n\n    public WorldgenRandom.Positional forkPositional() {\n        return new LegacyPositionalRandom(this.nextLong());\n    }\n\n    public int next(int max) {\n        long nextSeed = this.seed * 25214903917L + 11L & 281474976710655L;\n        this.seed = nextSeed;\n        return (int) (nextSeed >> 48 - max);\n    }\n\n    private record LegacyPositionalRandom(long seed) implements WorldgenRandom.Positional {\n\n        @Override\n        public WorldgenRandom fromHashOf(String name) {\n            return fromSeed(name.hashCode());\n        }\n\n        @Override\n        public WorldgenRandom fromSeed(long seed) {\n            return new LegacyRandom(seed ^ this.seed);\n        }\n\n        @Override\n        public long[] seedKey() {\n            return new long[0];\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/random/MarsagliaPolarGaussian.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.random;\n\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nclass MarsagliaPolarGaussian {\n    public final WorldgenRandom random;\n    private double nextGaussian;\n    private boolean hasNextGaussian;\n\n    public MarsagliaPolarGaussian(WorldgenRandom random) {\n        this.random = random;\n    }\n\n    public void reset() {\n        this.hasNextGaussian = false;\n    }\n\n    public double nextGaussian() {\n        if (this.hasNextGaussian) {\n            this.hasNextGaussian = false;\n            return this.nextGaussian;\n        } else {\n            double a;\n            double b;\n            double c;\n            do {\n                do {\n                    a = 2.0 * random.nextDouble() - 1.0;\n                    b = 2.0 * random.nextDouble() - 1.0;\n                    c = Util.square(a) + Util.square(b);\n                } while(c >= 1.0);\n            } while(c == 0.0);\n\n            double $$3 = Math.sqrt(-2.0 * Math.log(c) / c);\n            this.nextGaussian = b * $$3;\n            this.hasNextGaussian = true;\n            return a * $$3;\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/random/WorldgenRandom.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.random;\n\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.util.random.RandomGenerator;\n\npublic interface WorldgenRandom extends RandomGenerator {\n    static WorldgenRandom xoroshiro(long seed) {\n        return new XoroshiroRandom(seed);\n    }\n\n    static WorldgenRandom legacy(long i) {\n        return new LegacyRandom(i);\n    }\n\n    long nextLong();\n\n    default void consumeInt(int i) {\n        for (int j = 0; j < i; j++) {\n            nextInt();\n        }\n    }\n\n    default void consumeLong(int i) {\n        for (int j = 0; j < i; j++) {\n            nextLong();\n        }\n    }\n\n    WorldgenRandom fork();\n\n    Positional forkPositional();\n\n    interface Positional {\n\n        default WorldgenRandom at(int x, int y, int z) {\n            return fromSeed(Util.getSeed(x, y, z));\n        }\n\n        WorldgenRandom fromHashOf(String name);\n        WorldgenRandom fromSeed(long seed);\n\n        long[] seedKey();\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/random/XoroshiroPositionalRandom.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.random;\n\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\nrecord XoroshiroPositionalRandom(long seedLow, long seedHigh) implements WorldgenRandom.Positional {\n\n    @Override\n    public WorldgenRandom fromHashOf(String name) {\n        try {\n            var messageDigest = MessageDigest.getInstance(\"MD5\");\n            messageDigest.update(name.getBytes());\n            byte[] hash = messageDigest.digest();\n            long lo = Util.longfromBytes(hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7]);\n            long hi = Util.longfromBytes(hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]);\n            return new XoroshiroRandom(lo ^ this.seedLow, hi ^ this.seedHigh);\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public WorldgenRandom fromSeed(long seed) {\n        return new XoroshiroRandom(seed ^ seedLow, seedHigh);\n    }\n\n    @Override\n    public long[] seedKey() {\n        return new long[]{seedLow, seedHigh};\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/random/XoroshiroRandom.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.random;\n\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\npublic class XoroshiroRandom implements WorldgenRandom {\n\n    private final Xoroshiro128PlusPlus xpp;\n\n    public XoroshiroRandom(long seed) {\n        this(Util.extract128Seed(seed).mixed());\n    }\n\n    public XoroshiroRandom(Util.Seed seed) {\n        this(seed.low(), seed.high());\n    }\n\n    public XoroshiroRandom(long seedLow, long seedHigh) {\n        this.xpp = new Xoroshiro128PlusPlus(seedLow, seedHigh);\n    }\n\n    @Override\n    public long nextLong() {\n        return this.xpp.nextLong();\n    }\n\n    @Override\n    public int nextInt() {\n        return (int) this.xpp.nextLong();\n    }\n\n    @Override\n    public int nextInt(int bound) {\n        if (bound <= 0) {\n            throw new IllegalArgumentException(\"Bound must be positive\");\n        }\n\n        long nextUInt = Integer.toUnsignedLong(this.nextInt());\n        long random = nextUInt * (long) bound;\n        long limit = random & 4294967295L;\n        if (limit < (long) bound) {\n            for (int remainder = Integer.remainderUnsigned(~bound + 1, bound); limit < (long) remainder; limit = random & 0xffffffffL) {\n                nextUInt = Integer.toUnsignedLong(this.nextInt());\n                random = nextUInt * (long)bound;\n            }\n        }\n\n        return (int) (random >> 32);\n    }\n\n    @Override\n    public float nextFloat() {\n        return (float) this.nextBits(24) * 0x1.0p-24f;\n    }\n\n    @Override\n    public double nextDouble() {\n        return (double) this.nextBits(53) * 0x1.0p-53;\n    }\n\n    private long nextBits(int bitCount) {\n        return this.xpp.nextLong() >>> 64 - bitCount;\n    }\n\n    @Override\n    public WorldgenRandom fork() {\n        return new XoroshiroRandom(xpp.nextLong(), xpp.nextLong());\n    }\n\n    @Override\n    public Positional forkPositional() {\n        return new XoroshiroPositionalRandom(xpp.nextLong(), xpp.nextLong());\n    }\n\n    private static class Xoroshiro128PlusPlus {\n        private long seedLow;\n        private long seedHigh;\n\n        public Xoroshiro128PlusPlus(long seedLow, long seedHigh) {\n            this.seedLow = seedLow;\n            this.seedHigh = seedHigh;\n            if ((this.seedLow | this.seedHigh) == 0L) {\n                this.seedLow = -7046029254386353131L;\n                this.seedHigh = 7640891576956012809L;\n            }\n        }\n\n        public long nextLong() {\n            long low = this.seedLow;\n            long high = this.seedHigh;\n            long $$2 = Long.rotateLeft(low + high, 17) + low;\n            high ^= low;\n            this.seedLow = Long.rotateLeft(low, 49) ^ high ^ high << 21;\n            this.seedHigh = Long.rotateLeft(high, 28);\n            return $$2;\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/storage/DoubleStorage.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.storage;\n\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\n\nimport java.util.function.Supplier;\n\npublic interface DoubleStorage {\n\n    double obtain(int x, int y, int z);\n\n    static DoubleStorage from(DensityFunction densityFunction) {\n        return (x, y, z) -> densityFunction.compute(DensityFunction.context(x, y, z));\n    }\n\n    /**\n     * A storage that caches an exact, unique value for each 3d coordinate once.\n     * @return a new storage that caches the original\n     */\n    default DoubleStorage cache() {\n        return new DoubleStorageCache(this);\n    }\n\n    /**\n     * A storage that caches an exact, unique value for the 2d coordinate (x, z) once.\n     * @return a new storage that caches the original\n     */\n    default DoubleStorage cache2d() {\n        return new DoubleStorageCache2d(this);\n    }\n\n    static DoubleStorage threadLocal(Supplier<DoubleStorage> supplier) {\n        return new DoubleStorageThreadLocalImpl(supplier);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/storage/DoubleStorageCache.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.storage;\n\nimport it.unimi.dsi.fastutil.longs.Long2DoubleMap;\nimport it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;\n\nclass DoubleStorageCache implements DoubleStorage {\n\n    private final DoubleStorage original;\n\n    // The storage\n    private final Long2DoubleMap storage = new Long2DoubleOpenHashMap();\n    public DoubleStorageCache(DoubleStorage original) {\n        this.original = original;\n    }\n\n    @Override\n    public double obtain(int x, int y, int z) {\n        long index = getIndex(x, y, z);\n        return storage.computeIfAbsent(index, i -> original.obtain(x, y, z));\n    }\n\n    private long getIndex(int x, int y, int z) {\n        // 64 bits, separated into 3x 21 bits\n        // 21 bits for x, 21 bits for y, 21 bits for z\n        long index = 0;\n        index |= (x & 0x1FFFFF);\n        index |= ((long) (y & 0x1FFFFF) << 21);\n        index |= ((long) (z & 0x1FFFFF) << 42);\n        return index;\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/storage/DoubleStorageCache2d.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.storage;\n\nimport it.unimi.dsi.fastutil.longs.Long2DoubleMap;\nimport it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;\nimport net.minestom.server.coordinate.CoordConversion;\n\nclass DoubleStorageCache2d implements DoubleStorage {\n\n    private final DoubleStorage original;\n    private final Long2DoubleMap storage = new Long2DoubleOpenHashMap();\n    public DoubleStorageCache2d(DoubleStorage original) {\n        this.original = original;\n    }\n\n\n    @Override\n    public double obtain(int x, int y, int z) {\n        return storage.computeIfAbsent(getIndex(x, z), i -> original.obtain(x, y, z));\n    }\n\n    private long getIndex(int x, int z) {\n        return CoordConversion.chunkIndex(x, z);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/storage/DoubleStorageThreadLocalImpl.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.storage;\n\nimport java.util.function.Supplier;\n\nclass DoubleStorageThreadLocalImpl implements DoubleStorage {\n\n    private final ThreadLocal<DoubleStorage> threadLocal;\n    public DoubleStorageThreadLocalImpl(Supplier<DoubleStorage> supplier) {\n        this.threadLocal = ThreadLocal.withInitial(supplier);\n    }\n\n    @Override\n    public double obtain(int x, int y, int z) {\n        return threadLocal.get().obtain(x, y, z);\n    }\n}\n"
  },
  {
    "path": "datapack-loading/src/main/java/net/minestom/vanilla/datapack/worldgen/util/Util.java",
    "content": "package net.minestom.vanilla.datapack.worldgen.util;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Chunk;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.function.*;\nimport java.util.stream.StreamSupport;\n\npublic class Util {\n    public static double square(double x) {\n        return x * x;\n    }\n\n    public static double cube(double x) {\n        return x * x * x;\n    }\n\n    public static double clamp(double x, double min, double max) {\n        return x < min ? min : Math.min(x, max);\n    }\n\n    public static double lerp(double a, double b, double c) {\n        return b + a * (c - b);\n    }\n\n    public static double lerp2(double a, double b, double c, double d, double e, double f) {\n        return lerp(b, lerp(a, c, d), lerp(a, e, f));\n    }\n\n    public static double lerp3(double a, double b, double c, double d, double e, double f, double g, double h, double i, double j, double k) {\n        return lerp(c, lerp2(a, b, d, e, f, g), lerp2(a, b, h, i, j, k));\n    }\n\n    public static double lazyLerp(double a, DoubleSupplier b, DoubleSupplier c) {\n        if (a == 1) return c.getAsDouble();\n        if (a == 0) return b.getAsDouble();\n        double b_value = b.getAsDouble();\n        return b_value + a * (c.getAsDouble() - b_value);\n    }\n\n    public static double lazyLerp2(double a, double b, DoubleSupplier c, DoubleSupplier d, DoubleSupplier e,\n                                   DoubleSupplier f) {\n        return lazyLerp(b, () -> lazyLerp(a, c, d), () -> lazyLerp(a, e, f));\n    }\n\n    public static double lazyLerp3(double a, double b, double c, DoubleSupplier d, DoubleSupplier e, DoubleSupplier f,\n                                   DoubleSupplier g, DoubleSupplier h, DoubleSupplier i, DoubleSupplier j,\n                                   DoubleSupplier k) {\n        return lazyLerp(c, () -> lazyLerp2(a, b, d, e, f, g), () -> lazyLerp2(a, b, h, i, j, k));\n    }\n\n    public static double clampedLerp(double a, double b, double c) {\n        if (c < 0) {\n            return a;\n        } else if (c > 1) {\n            return b;\n        } else {\n            return lerp(c, a, b);\n        }\n    }\n\n    public static double inverseLerp(double a, double b, double c) {\n        return (a - b) / (c - b);\n    }\n\n    public static double smoothstep(double x) {\n        return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n    }\n\n    public static double map(double a, double b, double c, double d, double e) {\n        return lerp(inverseLerp(a, b, c), d, e);\n    }\n\n    public static double clampedMap(double a, double b, double c, double d, double e) {\n        return clampedLerp(d, e, inverseLerp(a, b, c));\n    }\n\n    /**\n     * Finds the index of the first value that matches the predicate\n     * @param min inclusive\n     * @param max exclusive\n     */\n    public static int binarySearch(int min, int max, IntPredicate predicate) {\n        // TODO: Make this an actual binary search\n        // slow version\n        for (int i = min; i < max; i++) {\n            if (predicate.test(i)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public static long getSeed(int x, int y, int z) {\n        long seed = (x * 3129871L) ^ (long) z * 116129781L ^ (long) y;\n        seed = seed * seed * 42317861L + seed * 11L;\n        return seed >> 16;\n    }\n\n    public static long longfromBytes(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h) {\n        return (long) (a) << (long) (56)\n                | (long) (b) << (long) (48)\n                | (long) (c) << (long) (40)\n                | (long) (d) << (long) (32)\n                | (long) (e) << (long) (24)\n                | (long) (f) << (long) (16)\n                | (long) (g) << (long) (8)\n                | (long) (h);\n    }\n\n    public static <T> @NotNull T jsonRequire(JsonObject root, String key, Function<JsonElement, T> mapper) {\n        JsonElement element = root.get(key);\n        if (element == null) {\n            throw new IllegalArgumentException(\"Missing required key \" + key);\n        }\n        return mapper.apply(element);\n    }\n\n    public static JsonArray jsonArray(JsonElement element) {\n        if (element.isJsonArray()) {\n            return element.getAsJsonArray();\n        }\n        throw new IllegalArgumentException(\"Expected array, got \" + element);\n    }\n\n    public static <T> List<T> jsonArray(JsonElement element, Function<JsonElement, T> mapper) {\n        if (element.isJsonArray()) {\n            return StreamSupport.stream(element.getAsJsonArray().spliterator(), false)\n                    .map(mapper)\n                    .toList();\n        }\n        throw new IllegalArgumentException(\"Expected array, got \" + element);\n    }\n\n    public static <T> @NotNull T jsonElse(JsonObject root, String key, T defaultValue, Function<JsonElement, T> mapper) {\n        JsonElement element = root.get(key);\n        if (element == null) {\n            return defaultValue;\n        }\n        return mapper.apply(element);\n    }\n\n    public static <T> Supplier<T> lazy(Supplier<T> supplier) {\n        return new Supplier<>() {\n            private T value;\n\n            @Override\n            public T get() {\n                if (value == null) {\n                    value = supplier.get();\n                }\n                return value;\n            }\n        };\n    }\n\n    public static IntSupplier lazyInt(IntSupplier supplier) {\n        return new IntSupplier() {\n            private int value;\n\n            @Override\n            public int getAsInt() {\n                if (value == 0) {\n                    value = supplier.getAsInt();\n                }\n                return value;\n            }\n        };\n    }\n\n    public static DoubleSupplier lazyDouble(DoubleSupplier supplier) {\n        return new DoubleSupplier() {\n            private double value;\n\n            @Override\n            public double getAsDouble() {\n                if (value == 0) {\n                    value = supplier.getAsDouble();\n                }\n                return value;\n            }\n        };\n    }\n\n    public static JsonObject jsonObject(Object obj) {\n        if (obj instanceof String str)\n            return jsonObject(new Gson().fromJson(str, JsonElement.class));\n        if (!(obj instanceof JsonObject object))\n            throw new IllegalArgumentException(\"Expected a JsonObject, got \" + obj.getClass().getName());\n        return object;\n    }\n\n    public static int chunkMinX(Point chunkPos) {\n        int chunkX = chunkPos.chunkX();\n        return chunkX * Chunk.CHUNK_SIZE_X;\n    }\n\n    public static int chunkMinZ(Point chunkPos) {\n        int chunkZ = chunkPos.chunkZ();\n        return chunkZ * Chunk.CHUNK_SIZE_Z;\n    }\n\n    public static int chunkMaxX(Point chunkPos) {\n        return chunkMinX(chunkPos) + Chunk.CHUNK_SIZE_X;\n    }\n\n    public static int chunkMaxZ(Point chunkPos) {\n        return chunkMinZ(chunkPos) + Chunk.CHUNK_SIZE_Z;\n    }\n\n    public record Seed(long low, long high) {\n        public Seed mixed() {\n            return new Seed(staffordMix13(low), staffordMix13(high));\n        }\n    }\n\n    public static Seed extract128Seed(long originalSeed) {\n        long low = originalSeed ^ 0x6a09e667f3bcc909L;\n        long high = low - 0x61c8864680b583ebL;\n        return new Seed(low, high);\n    }\n\n    /* David Stafford's (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html)\n     * \"Mix13\" variant of the 64-bit finalizer in Austin Appleby's MurmurHash3 algorithm. */\n    public static long staffordMix13(long z) {\n        z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;\n        z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;\n        return z ^ (z >>> 31);\n    }\n}"
  },
  {
    "path": "datapack-tests/build.gradle.kts",
    "content": "plugins {\n    id(\"org.spongepowered.gradle.vanilla\") version \"0.2.1-SNAPSHOT\"\n}\n\ndependencies {\n    compileOnly(project(\":core\"))\n    testImplementation(platform(\"org.junit:junit-bom:5.9.1\"))\n    testImplementation(\"org.junit.jupiter:junit-jupiter\")\n    testImplementation(project(\":core\"))\n    testImplementation(project(\":datapack-loading\"))\n    testImplementation(project(\":blocks\"))\n    testImplementation(project(\":block-update-system\"))\n    testImplementation(project(\":mojang-data\"))\n}\n\ntasks.test {\n    useJUnitPlatform()\n}\n\nminecraft {\n    version(\"1.21.5\")\n    runs {\n        server()\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/loot/LootTableTestData.java",
    "content": "package net.minestom.vanilla.datapack.loot;\n\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class LootTableTestData {\n\n    public static final Map<String, List<ItemStack>> EXPECTED_RESULTS;\n\n    static {\n        Map<String, List<ItemStack>> expected = new HashMap<>();\n\n        // TODO: Go through these and make sure they are correct\n        expected.put(\"acacia_button\", List.of(ItemStack.of(Material.ACACIA_BUTTON, 1)));\n        expected.put(\"acacia_door\", List.of());\n        expected.put(\"acacia_fence\", List.of(ItemStack.of(Material.ACACIA_FENCE, 1)));\n        expected.put(\"acacia_fence_gate\", List.of(ItemStack.of(Material.ACACIA_FENCE_GATE, 1)));\n        expected.put(\"acacia_hanging_sign\", List.of(ItemStack.of(Material.ACACIA_HANGING_SIGN, 1)));\n        expected.put(\"acacia_leaves\", List.of());\n        expected.put(\"acacia_log\", List.of(ItemStack.of(Material.ACACIA_LOG, 1)));\n        expected.put(\"acacia_planks\", List.of(ItemStack.of(Material.ACACIA_PLANKS, 1)));\n        expected.put(\"acacia_pressure_plate\", List.of(ItemStack.of(Material.ACACIA_PRESSURE_PLATE, 1)));\n        expected.put(\"acacia_sapling\", List.of(ItemStack.of(Material.ACACIA_SAPLING, 1)));\n        expected.put(\"acacia_sign\", List.of(ItemStack.of(Material.ACACIA_SIGN, 1)));\n        expected.put(\"acacia_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"acacia_stairs\", List.of(ItemStack.of(Material.ACACIA_STAIRS, 1)));\n        expected.put(\"acacia_trapdoor\", List.of(ItemStack.of(Material.ACACIA_TRAPDOOR, 1)));\n        expected.put(\"acacia_wood\", List.of(ItemStack.of(Material.ACACIA_WOOD, 1)));\n        expected.put(\"activator_rail\", List.of(ItemStack.of(Material.ACTIVATOR_RAIL, 1)));\n        expected.put(\"allium\", List.of(ItemStack.of(Material.ALLIUM, 1)));\n        expected.put(\"amethyst_block\", List.of(ItemStack.of(Material.AMETHYST_BLOCK, 1)));\n        expected.put(\"amethyst_cluster\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"ancient_debris\", List.of(ItemStack.of(Material.ANCIENT_DEBRIS, 1)));\n        expected.put(\"andesite\", List.of(ItemStack.of(Material.ANDESITE, 1)));\n        expected.put(\"andesite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"andesite_stairs\", List.of(ItemStack.of(Material.ANDESITE_STAIRS, 1)));\n        expected.put(\"andesite_wall\", List.of(ItemStack.of(Material.ANDESITE_WALL, 1)));\n        expected.put(\"anvil\", List.of(ItemStack.of(Material.ANVIL, 1)));\n        expected.put(\"attached_melon_stem\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"attached_pumpkin_stem\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"azalea\", List.of(ItemStack.of(Material.AZALEA, 1)));\n        expected.put(\"azalea_leaves\", List.of());\n        expected.put(\"azure_bluet\", List.of(ItemStack.of(Material.AZURE_BLUET, 1)));\n        expected.put(\"bamboo\", List.of(ItemStack.of(Material.BAMBOO, 1)));\n        expected.put(\"bamboo_block\", List.of(ItemStack.of(Material.BAMBOO_BLOCK, 1)));\n        expected.put(\"bamboo_button\", List.of(ItemStack.of(Material.BAMBOO_BUTTON, 1)));\n        expected.put(\"bamboo_door\", List.of());\n        expected.put(\"bamboo_fence\", List.of(ItemStack.of(Material.BAMBOO_FENCE, 1)));\n        expected.put(\"bamboo_fence_gate\", List.of(ItemStack.of(Material.BAMBOO_FENCE_GATE, 1)));\n        expected.put(\"bamboo_hanging_sign\", List.of(ItemStack.of(Material.BAMBOO_HANGING_SIGN, 1)));\n        expected.put(\"bamboo_mosaic\", List.of(ItemStack.of(Material.BAMBOO_MOSAIC, 1)));\n        expected.put(\"bamboo_mosaic_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"bamboo_mosaic_stairs\", List.of(ItemStack.of(Material.BAMBOO_MOSAIC_STAIRS, 1)));\n        expected.put(\"bamboo_planks\", List.of(ItemStack.of(Material.BAMBOO_PLANKS, 1)));\n        expected.put(\"bamboo_pressure_plate\", List.of(ItemStack.of(Material.BAMBOO_PRESSURE_PLATE, 1)));\n        expected.put(\"bamboo_sapling\", List.of(ItemStack.of(Material.BAMBOO, 1)));\n        expected.put(\"bamboo_sign\", List.of(ItemStack.of(Material.BAMBOO_SIGN, 1)));\n        expected.put(\"bamboo_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"bamboo_stairs\", List.of(ItemStack.of(Material.BAMBOO_STAIRS, 1)));\n        expected.put(\"bamboo_trapdoor\", List.of(ItemStack.of(Material.BAMBOO_TRAPDOOR, 1)));\n        expected.put(\"barrel\", List.of(ItemStack.of(Material.BARREL, 1)));\n        expected.put(\"basalt\", List.of(ItemStack.of(Material.BASALT, 1)));\n        expected.put(\"beacon\", List.of(ItemStack.of(Material.BEACON, 1)));\n        expected.put(\"bee_nest\", List.of());\n        expected.put(\"beehive\", List.of(ItemStack.of(Material.BEEHIVE, 1)));\n        expected.put(\"beetroots\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"bell\", List.of(ItemStack.of(Material.BELL, 1)));\n        expected.put(\"big_dripleaf\", List.of(ItemStack.of(Material.BIG_DRIPLEAF, 1)));\n        expected.put(\"big_dripleaf_stem\", List.of(ItemStack.of(Material.BIG_DRIPLEAF, 1)));\n        expected.put(\"birch_button\", List.of(ItemStack.of(Material.BIRCH_BUTTON, 1)));\n        expected.put(\"birch_door\", List.of());\n        expected.put(\"birch_fence\", List.of(ItemStack.of(Material.BIRCH_FENCE, 1)));\n        expected.put(\"birch_fence_gate\", List.of(ItemStack.of(Material.BIRCH_FENCE_GATE, 1)));\n        expected.put(\"birch_hanging_sign\", List.of(ItemStack.of(Material.BIRCH_HANGING_SIGN, 1)));\n        expected.put(\"birch_leaves\", List.of());\n        expected.put(\"birch_log\", List.of(ItemStack.of(Material.BIRCH_LOG, 1)));\n        expected.put(\"birch_planks\", List.of(ItemStack.of(Material.BIRCH_PLANKS, 1)));\n        expected.put(\"birch_pressure_plate\", List.of(ItemStack.of(Material.BIRCH_PRESSURE_PLATE, 1)));\n        expected.put(\"birch_sapling\", List.of(ItemStack.of(Material.BIRCH_SAPLING, 1)));\n        expected.put(\"birch_sign\", List.of(ItemStack.of(Material.BIRCH_SIGN, 1)));\n        expected.put(\"birch_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"birch_stairs\", List.of(ItemStack.of(Material.BIRCH_STAIRS, 1)));\n        expected.put(\"birch_trapdoor\", List.of(ItemStack.of(Material.BIRCH_TRAPDOOR, 1)));\n        expected.put(\"birch_wood\", List.of(ItemStack.of(Material.BIRCH_WOOD, 1)));\n        expected.put(\"black_banner\", List.of(ItemStack.of(Material.BLACK_BANNER, 1)));\n        expected.put(\"black_bed\", List.of());\n        expected.put(\"black_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"black_candle_cake\", List.of(ItemStack.of(Material.BLACK_CANDLE, 1)));\n        expected.put(\"black_carpet\", List.of(ItemStack.of(Material.BLACK_CARPET, 1)));\n        expected.put(\"black_concrete\", List.of(ItemStack.of(Material.BLACK_CONCRETE, 1)));\n        expected.put(\"black_concrete_powder\", List.of(ItemStack.of(Material.BLACK_CONCRETE_POWDER, 1)));\n        expected.put(\"black_glazed_terracotta\", List.of(ItemStack.of(Material.BLACK_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"black_shulker_box\", List.of(ItemStack.of(Material.BLACK_SHULKER_BOX, 1)));\n        expected.put(\"black_stained_glass\", List.of());\n        expected.put(\"black_stained_glass_pane\", List.of());\n        expected.put(\"black_terracotta\", List.of(ItemStack.of(Material.BLACK_TERRACOTTA, 1)));\n        expected.put(\"black_wool\", List.of(ItemStack.of(Material.BLACK_WOOL, 1)));\n        expected.put(\"blackstone\", List.of(ItemStack.of(Material.BLACKSTONE, 1)));\n        expected.put(\"blackstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"blackstone_stairs\", List.of(ItemStack.of(Material.BLACKSTONE_STAIRS, 1)));\n        expected.put(\"blackstone_wall\", List.of(ItemStack.of(Material.BLACKSTONE_WALL, 1)));\n        expected.put(\"blast_furnace\", List.of(ItemStack.of(Material.BLAST_FURNACE, 1)));\n        expected.put(\"blue_banner\", List.of(ItemStack.of(Material.BLUE_BANNER, 1)));\n        expected.put(\"blue_bed\", List.of());\n        expected.put(\"blue_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"blue_candle_cake\", List.of(ItemStack.of(Material.BLUE_CANDLE, 1)));\n        expected.put(\"blue_carpet\", List.of(ItemStack.of(Material.BLUE_CARPET, 1)));\n        expected.put(\"blue_concrete\", List.of(ItemStack.of(Material.BLUE_CONCRETE, 1)));\n        expected.put(\"blue_concrete_powder\", List.of(ItemStack.of(Material.BLUE_CONCRETE_POWDER, 1)));\n        expected.put(\"blue_glazed_terracotta\", List.of(ItemStack.of(Material.BLUE_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"blue_ice\", List.of());\n        expected.put(\"blue_orchid\", List.of(ItemStack.of(Material.BLUE_ORCHID, 1)));\n        expected.put(\"blue_shulker_box\", List.of(ItemStack.of(Material.BLUE_SHULKER_BOX, 1)));\n        expected.put(\"blue_stained_glass\", List.of());\n        expected.put(\"blue_stained_glass_pane\", List.of());\n        expected.put(\"blue_terracotta\", List.of(ItemStack.of(Material.BLUE_TERRACOTTA, 1)));\n        expected.put(\"blue_wool\", List.of(ItemStack.of(Material.BLUE_WOOL, 1)));\n        expected.put(\"bone_block\", List.of(ItemStack.of(Material.BONE_BLOCK, 1)));\n        expected.put(\"bookshelf\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"brain_coral\", List.of());\n        expected.put(\"brain_coral_block\", List.of(ItemStack.of(Material.DEAD_BRAIN_CORAL_BLOCK, 1)));\n        expected.put(\"brain_coral_fan\", List.of());\n        expected.put(\"brewing_stand\", List.of(ItemStack.of(Material.BREWING_STAND, 1)));\n        expected.put(\"brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"brick_stairs\", List.of(ItemStack.of(Material.BRICK_STAIRS, 1)));\n        expected.put(\"brick_wall\", List.of(ItemStack.of(Material.BRICK_WALL, 1)));\n        expected.put(\"bricks\", List.of(ItemStack.of(Material.BRICKS, 1)));\n        expected.put(\"brown_banner\", List.of(ItemStack.of(Material.BROWN_BANNER, 1)));\n        expected.put(\"brown_bed\", List.of());\n        expected.put(\"brown_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"brown_candle_cake\", List.of(ItemStack.of(Material.BROWN_CANDLE, 1)));\n        expected.put(\"brown_carpet\", List.of(ItemStack.of(Material.BROWN_CARPET, 1)));\n        expected.put(\"brown_concrete\", List.of(ItemStack.of(Material.BROWN_CONCRETE, 1)));\n        expected.put(\"brown_concrete_powder\", List.of(ItemStack.of(Material.BROWN_CONCRETE_POWDER, 1)));\n        expected.put(\"brown_glazed_terracotta\", List.of(ItemStack.of(Material.BROWN_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"brown_mushroom\", List.of(ItemStack.of(Material.BROWN_MUSHROOM, 1)));\n        expected.put(\"brown_mushroom_block\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"brown_shulker_box\", List.of(ItemStack.of(Material.BROWN_SHULKER_BOX, 1)));\n        expected.put(\"brown_stained_glass\", List.of());\n        expected.put(\"brown_stained_glass_pane\", List.of());\n        expected.put(\"brown_terracotta\", List.of(ItemStack.of(Material.BROWN_TERRACOTTA, 1)));\n        expected.put(\"brown_wool\", List.of(ItemStack.of(Material.BROWN_WOOL, 1)));\n        expected.put(\"bubble_coral\", List.of());\n        expected.put(\"bubble_coral_block\", List.of(ItemStack.of(Material.DEAD_BUBBLE_CORAL_BLOCK, 1)));\n        expected.put(\"bubble_coral_fan\", List.of());\n        expected.put(\"budding_amethyst\", List.of());\n        expected.put(\"cactus\", List.of(ItemStack.of(Material.CACTUS, 1)));\n        expected.put(\"cake\", List.of());\n        expected.put(\"calcite\", List.of(ItemStack.of(Material.CALCITE, 1)));\n        expected.put(\"calibrated_sculk_sensor\", List.of());\n        expected.put(\"campfire\", List.of(ItemStack.of(Material.CHARCOAL, 2)));\n        expected.put(\"candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"candle_cake\", List.of(ItemStack.of(Material.CANDLE, 1)));\n        expected.put(\"carrots\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cartography_table\", List.of(ItemStack.of(Material.CARTOGRAPHY_TABLE, 1)));\n        expected.put(\"carved_pumpkin\", List.of(ItemStack.of(Material.CARVED_PUMPKIN, 1)));\n        expected.put(\"cauldron\", List.of(ItemStack.of(Material.CAULDRON, 1)));\n        expected.put(\"cave_vines\", List.of());\n        expected.put(\"cave_vines_plant\", List.of());\n        expected.put(\"chain\", List.of(ItemStack.of(Material.CHAIN, 1)));\n        expected.put(\"cherry_button\", List.of(ItemStack.of(Material.CHERRY_BUTTON, 1)));\n        expected.put(\"cherry_door\", List.of());\n        expected.put(\"cherry_fence\", List.of(ItemStack.of(Material.CHERRY_FENCE, 1)));\n        expected.put(\"cherry_fence_gate\", List.of(ItemStack.of(Material.CHERRY_FENCE_GATE, 1)));\n        expected.put(\"cherry_hanging_sign\", List.of(ItemStack.of(Material.CHERRY_HANGING_SIGN, 1)));\n        expected.put(\"cherry_leaves\", List.of());\n        expected.put(\"cherry_log\", List.of(ItemStack.of(Material.CHERRY_LOG, 1)));\n        expected.put(\"cherry_planks\", List.of(ItemStack.of(Material.CHERRY_PLANKS, 1)));\n        expected.put(\"cherry_pressure_plate\", List.of(ItemStack.of(Material.CHERRY_PRESSURE_PLATE, 1)));\n        expected.put(\"cherry_sapling\", List.of(ItemStack.of(Material.CHERRY_SAPLING, 1)));\n        expected.put(\"cherry_sign\", List.of(ItemStack.of(Material.CHERRY_SIGN, 1)));\n        expected.put(\"cherry_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cherry_stairs\", List.of(ItemStack.of(Material.CHERRY_STAIRS, 1)));\n        expected.put(\"cherry_trapdoor\", List.of(ItemStack.of(Material.CHERRY_TRAPDOOR, 1)));\n        expected.put(\"cherry_wood\", List.of(ItemStack.of(Material.CHERRY_WOOD, 1)));\n        expected.put(\"chest\", List.of(ItemStack.of(Material.CHEST, 1)));\n        expected.put(\"chipped_anvil\", List.of(ItemStack.of(Material.CHIPPED_ANVIL, 1)));\n        expected.put(\"chiseled_bookshelf\", List.of());\n        expected.put(\"chiseled_copper\", List.of());\n        expected.put(\"chiseled_deepslate\", List.of(ItemStack.of(Material.CHISELED_DEEPSLATE, 1)));\n        expected.put(\"chiseled_nether_bricks\", List.of(ItemStack.of(Material.CHISELED_NETHER_BRICKS, 1)));\n        expected.put(\"chiseled_polished_blackstone\", List.of(ItemStack.of(Material.CHISELED_POLISHED_BLACKSTONE, 1)));\n        expected.put(\"chiseled_quartz_block\", List.of(ItemStack.of(Material.CHISELED_QUARTZ_BLOCK, 1)));\n        expected.put(\"chiseled_red_sandstone\", List.of(ItemStack.of(Material.CHISELED_RED_SANDSTONE, 1)));\n        expected.put(\"chiseled_sandstone\", List.of(ItemStack.of(Material.CHISELED_SANDSTONE, 1)));\n        expected.put(\"chiseled_stone_bricks\", List.of(ItemStack.of(Material.CHISELED_STONE_BRICKS, 1)));\n        expected.put(\"chiseled_tuff\", List.of());\n        expected.put(\"chiseled_tuff_bricks\", List.of());\n        expected.put(\"chorus_flower\", List.of());\n        expected.put(\"chorus_plant\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"clay\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"coal_block\", List.of(ItemStack.of(Material.COAL_BLOCK, 1)));\n        expected.put(\"coal_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"coarse_dirt\", List.of(ItemStack.of(Material.COARSE_DIRT, 1)));\n        expected.put(\"cobbled_deepslate\", List.of(ItemStack.of(Material.COBBLED_DEEPSLATE, 1)));\n        expected.put(\"cobbled_deepslate_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cobbled_deepslate_stairs\", List.of(ItemStack.of(Material.COBBLED_DEEPSLATE_STAIRS, 1)));\n        expected.put(\"cobbled_deepslate_wall\", List.of(ItemStack.of(Material.COBBLED_DEEPSLATE_WALL, 1)));\n        expected.put(\"cobblestone\", List.of(ItemStack.of(Material.COBBLESTONE, 1)));\n        expected.put(\"cobblestone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cobblestone_stairs\", List.of(ItemStack.of(Material.COBBLESTONE_STAIRS, 1)));\n        expected.put(\"cobblestone_wall\", List.of(ItemStack.of(Material.COBBLESTONE_WALL, 1)));\n        expected.put(\"cobweb\", List.of(ItemStack.of(Material.STRING, 1)));\n        expected.put(\"cocoa\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"comparator\", List.of(ItemStack.of(Material.COMPARATOR, 1)));\n        expected.put(\"composter\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"conduit\", List.of(ItemStack.of(Material.CONDUIT, 1)));\n        expected.put(\"copper_block\", List.of(ItemStack.of(Material.COPPER_BLOCK, 1)));\n        expected.put(\"copper_bulb\", List.of());\n        expected.put(\"copper_door\", List.of());\n        expected.put(\"copper_grate\", List.of());\n        expected.put(\"copper_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"copper_trapdoor\", List.of());\n        expected.put(\"cornflower\", List.of(ItemStack.of(Material.CORNFLOWER, 1)));\n        expected.put(\"cracked_deepslate_bricks\", List.of(ItemStack.of(Material.CRACKED_DEEPSLATE_BRICKS, 1)));\n        expected.put(\"cracked_deepslate_tiles\", List.of(ItemStack.of(Material.CRACKED_DEEPSLATE_TILES, 1)));\n        expected.put(\"cracked_nether_bricks\", List.of(ItemStack.of(Material.CRACKED_NETHER_BRICKS, 1)));\n        expected.put(\"cracked_polished_blackstone_bricks\", List.of(ItemStack.of(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, 1)));\n        expected.put(\"cracked_stone_bricks\", List.of(ItemStack.of(Material.CRACKED_STONE_BRICKS, 1)));\n        expected.put(\"crafter\", List.of());\n        expected.put(\"crafting_table\", List.of(ItemStack.of(Material.CRAFTING_TABLE, 1)));\n        expected.put(\"creeper_head\", List.of(ItemStack.of(Material.CREEPER_HEAD, 1)));\n        expected.put(\"crimson_button\", List.of(ItemStack.of(Material.CRIMSON_BUTTON, 1)));\n        expected.put(\"crimson_door\", List.of());\n        expected.put(\"crimson_fence\", List.of(ItemStack.of(Material.CRIMSON_FENCE, 1)));\n        expected.put(\"crimson_fence_gate\", List.of(ItemStack.of(Material.CRIMSON_FENCE_GATE, 1)));\n        expected.put(\"crimson_fungus\", List.of(ItemStack.of(Material.CRIMSON_FUNGUS, 1)));\n        expected.put(\"crimson_hanging_sign\", List.of(ItemStack.of(Material.CRIMSON_HANGING_SIGN, 1)));\n        expected.put(\"crimson_hyphae\", List.of(ItemStack.of(Material.CRIMSON_HYPHAE, 1)));\n        expected.put(\"crimson_nylium\", List.of(ItemStack.of(Material.NETHERRACK, 1)));\n        expected.put(\"crimson_planks\", List.of(ItemStack.of(Material.CRIMSON_PLANKS, 1)));\n        expected.put(\"crimson_pressure_plate\", List.of(ItemStack.of(Material.CRIMSON_PRESSURE_PLATE, 1)));\n        expected.put(\"crimson_roots\", List.of(ItemStack.of(Material.CRIMSON_ROOTS, 1)));\n        expected.put(\"crimson_sign\", List.of(ItemStack.of(Material.CRIMSON_SIGN, 1)));\n        expected.put(\"crimson_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"crimson_stairs\", List.of(ItemStack.of(Material.CRIMSON_STAIRS, 1)));\n        expected.put(\"crimson_stem\", List.of(ItemStack.of(Material.CRIMSON_STEM, 1)));\n        expected.put(\"crimson_trapdoor\", List.of(ItemStack.of(Material.CRIMSON_TRAPDOOR, 1)));\n        expected.put(\"crying_obsidian\", List.of(ItemStack.of(Material.CRYING_OBSIDIAN, 1)));\n        expected.put(\"cut_copper\", List.of(ItemStack.of(Material.CUT_COPPER, 1)));\n        expected.put(\"cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cut_copper_stairs\", List.of(ItemStack.of(Material.CUT_COPPER_STAIRS, 1)));\n        expected.put(\"cut_red_sandstone\", List.of(ItemStack.of(Material.CUT_RED_SANDSTONE, 1)));\n        expected.put(\"cut_red_sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cut_sandstone\", List.of(ItemStack.of(Material.CUT_SANDSTONE, 1)));\n        expected.put(\"cut_sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cyan_banner\", List.of(ItemStack.of(Material.CYAN_BANNER, 1)));\n        expected.put(\"cyan_bed\", List.of());\n        expected.put(\"cyan_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"cyan_candle_cake\", List.of(ItemStack.of(Material.CYAN_CANDLE, 1)));\n        expected.put(\"cyan_carpet\", List.of(ItemStack.of(Material.CYAN_CARPET, 1)));\n        expected.put(\"cyan_concrete\", List.of(ItemStack.of(Material.CYAN_CONCRETE, 1)));\n        expected.put(\"cyan_concrete_powder\", List.of(ItemStack.of(Material.CYAN_CONCRETE_POWDER, 1)));\n        expected.put(\"cyan_glazed_terracotta\", List.of(ItemStack.of(Material.CYAN_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"cyan_shulker_box\", List.of(ItemStack.of(Material.CYAN_SHULKER_BOX, 1)));\n        expected.put(\"cyan_stained_glass\", List.of());\n        expected.put(\"cyan_stained_glass_pane\", List.of());\n        expected.put(\"cyan_terracotta\", List.of(ItemStack.of(Material.CYAN_TERRACOTTA, 1)));\n        expected.put(\"cyan_wool\", List.of(ItemStack.of(Material.CYAN_WOOL, 1)));\n        expected.put(\"damaged_anvil\", List.of(ItemStack.of(Material.DAMAGED_ANVIL, 1)));\n        expected.put(\"dandelion\", List.of(ItemStack.of(Material.DANDELION, 1)));\n        expected.put(\"dark_oak_button\", List.of(ItemStack.of(Material.DARK_OAK_BUTTON, 1)));\n        expected.put(\"dark_oak_door\", List.of());\n        expected.put(\"dark_oak_fence\", List.of(ItemStack.of(Material.DARK_OAK_FENCE, 1)));\n        expected.put(\"dark_oak_fence_gate\", List.of(ItemStack.of(Material.DARK_OAK_FENCE_GATE, 1)));\n        expected.put(\"dark_oak_hanging_sign\", List.of(ItemStack.of(Material.DARK_OAK_HANGING_SIGN, 1)));\n        expected.put(\"dark_oak_leaves\", List.of());\n        expected.put(\"dark_oak_log\", List.of(ItemStack.of(Material.DARK_OAK_LOG, 1)));\n        expected.put(\"dark_oak_planks\", List.of(ItemStack.of(Material.DARK_OAK_PLANKS, 1)));\n        expected.put(\"dark_oak_pressure_plate\", List.of(ItemStack.of(Material.DARK_OAK_PRESSURE_PLATE, 1)));\n        expected.put(\"dark_oak_sapling\", List.of(ItemStack.of(Material.DARK_OAK_SAPLING, 1)));\n        expected.put(\"dark_oak_sign\", List.of(ItemStack.of(Material.DARK_OAK_SIGN, 1)));\n        expected.put(\"dark_oak_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"dark_oak_stairs\", List.of(ItemStack.of(Material.DARK_OAK_STAIRS, 1)));\n        expected.put(\"dark_oak_trapdoor\", List.of(ItemStack.of(Material.DARK_OAK_TRAPDOOR, 1)));\n        expected.put(\"dark_oak_wood\", List.of(ItemStack.of(Material.DARK_OAK_WOOD, 1)));\n        expected.put(\"dark_prismarine\", List.of(ItemStack.of(Material.DARK_PRISMARINE, 1)));\n        expected.put(\"dark_prismarine_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"dark_prismarine_stairs\", List.of(ItemStack.of(Material.DARK_PRISMARINE_STAIRS, 1)));\n        expected.put(\"daylight_detector\", List.of(ItemStack.of(Material.DAYLIGHT_DETECTOR, 1)));\n        expected.put(\"dead_brain_coral\", List.of());\n        expected.put(\"dead_brain_coral_block\", List.of(ItemStack.of(Material.DEAD_BRAIN_CORAL_BLOCK, 1)));\n        expected.put(\"dead_brain_coral_fan\", List.of());\n        expected.put(\"dead_bubble_coral\", List.of());\n        expected.put(\"dead_bubble_coral_block\", List.of(ItemStack.of(Material.DEAD_BUBBLE_CORAL_BLOCK, 1)));\n        expected.put(\"dead_bubble_coral_fan\", List.of());\n        expected.put(\"dead_bush\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"dead_fire_coral\", List.of());\n        expected.put(\"dead_fire_coral_block\", List.of(ItemStack.of(Material.DEAD_FIRE_CORAL_BLOCK, 1)));\n        expected.put(\"dead_fire_coral_fan\", List.of());\n        expected.put(\"dead_horn_coral\", List.of());\n        expected.put(\"dead_horn_coral_block\", List.of(ItemStack.of(Material.DEAD_HORN_CORAL_BLOCK, 1)));\n        expected.put(\"dead_horn_coral_fan\", List.of());\n        expected.put(\"dead_tube_coral\", List.of());\n        expected.put(\"dead_tube_coral_block\", List.of(ItemStack.of(Material.DEAD_TUBE_CORAL_BLOCK, 1)));\n        expected.put(\"dead_tube_coral_fan\", List.of());\n        expected.put(\"decorated_pot\", List.of(ItemStack.of(Material.DECORATED_POT, 1)));\n        expected.put(\"deepslate\", List.of(ItemStack.of(Material.COBBLED_DEEPSLATE, 1)));\n        expected.put(\"deepslate_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_brick_stairs\", List.of(ItemStack.of(Material.DEEPSLATE_BRICK_STAIRS, 1)));\n        expected.put(\"deepslate_brick_wall\", List.of(ItemStack.of(Material.DEEPSLATE_BRICK_WALL, 1)));\n        expected.put(\"deepslate_bricks\", List.of(ItemStack.of(Material.DEEPSLATE_BRICKS, 1)));\n        expected.put(\"deepslate_coal_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_copper_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_diamond_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_emerald_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_gold_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_iron_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_lapis_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_redstone_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_tile_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"deepslate_tile_stairs\", List.of(ItemStack.of(Material.DEEPSLATE_TILE_STAIRS, 1)));\n        expected.put(\"deepslate_tile_wall\", List.of(ItemStack.of(Material.DEEPSLATE_TILE_WALL, 1)));\n        expected.put(\"deepslate_tiles\", List.of(ItemStack.of(Material.DEEPSLATE_TILES, 1)));\n        expected.put(\"detector_rail\", List.of(ItemStack.of(Material.DETECTOR_RAIL, 1)));\n        expected.put(\"diamond_block\", List.of(ItemStack.of(Material.DIAMOND_BLOCK, 1)));\n        expected.put(\"diamond_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"diorite\", List.of(ItemStack.of(Material.DIORITE, 1)));\n        expected.put(\"diorite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"diorite_stairs\", List.of(ItemStack.of(Material.DIORITE_STAIRS, 1)));\n        expected.put(\"diorite_wall\", List.of(ItemStack.of(Material.DIORITE_WALL, 1)));\n        expected.put(\"dirt\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"dirt_path\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"dispenser\", List.of(ItemStack.of(Material.DISPENSER, 1)));\n        expected.put(\"dragon_egg\", List.of(ItemStack.of(Material.DRAGON_EGG, 1)));\n        expected.put(\"dragon_head\", List.of(ItemStack.of(Material.DRAGON_HEAD, 1)));\n        expected.put(\"dried_kelp_block\", List.of(ItemStack.of(Material.DRIED_KELP_BLOCK, 1)));\n        expected.put(\"dripstone_block\", List.of(ItemStack.of(Material.DRIPSTONE_BLOCK, 1)));\n        expected.put(\"dropper\", List.of(ItemStack.of(Material.DROPPER, 1)));\n        expected.put(\"emerald_block\", List.of(ItemStack.of(Material.EMERALD_BLOCK, 1)));\n        expected.put(\"emerald_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"enchanting_table\", List.of(ItemStack.of(Material.ENCHANTING_TABLE, 1)));\n        expected.put(\"end_rod\", List.of(ItemStack.of(Material.END_ROD, 1)));\n        expected.put(\"end_stone\", List.of(ItemStack.of(Material.END_STONE, 1)));\n        expected.put(\"end_stone_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"end_stone_brick_stairs\", List.of(ItemStack.of(Material.END_STONE_BRICK_STAIRS, 1)));\n        expected.put(\"end_stone_brick_wall\", List.of(ItemStack.of(Material.END_STONE_BRICK_WALL, 1)));\n        expected.put(\"end_stone_bricks\", List.of(ItemStack.of(Material.END_STONE_BRICKS, 1)));\n        expected.put(\"ender_chest\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"exposed_chiseled_copper\", List.of());\n        expected.put(\"exposed_copper\", List.of(ItemStack.of(Material.EXPOSED_COPPER, 1)));\n        expected.put(\"exposed_copper_bulb\", List.of());\n        expected.put(\"exposed_copper_door\", List.of());\n        expected.put(\"exposed_copper_grate\", List.of());\n        expected.put(\"exposed_copper_trapdoor\", List.of());\n        expected.put(\"exposed_cut_copper\", List.of(ItemStack.of(Material.EXPOSED_CUT_COPPER, 1)));\n        expected.put(\"exposed_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"exposed_cut_copper_stairs\", List.of(ItemStack.of(Material.EXPOSED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"farmland\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"fern\", List.of());\n        expected.put(\"fire\", List.of());\n        expected.put(\"fire_coral\", List.of());\n        expected.put(\"fire_coral_block\", List.of(ItemStack.of(Material.DEAD_FIRE_CORAL_BLOCK, 1)));\n        expected.put(\"fire_coral_fan\", List.of());\n        expected.put(\"fletching_table\", List.of(ItemStack.of(Material.FLETCHING_TABLE, 1)));\n        expected.put(\"flower_pot\", List.of(ItemStack.of(Material.FLOWER_POT, 1)));\n        expected.put(\"flowering_azalea\", List.of(ItemStack.of(Material.FLOWERING_AZALEA, 1)));\n        expected.put(\"flowering_azalea_leaves\", List.of());\n        expected.put(\"frogspawn\", List.of());\n        expected.put(\"frosted_ice\", List.of());\n        expected.put(\"furnace\", List.of(ItemStack.of(Material.FURNACE, 1)));\n        expected.put(\"gilded_blackstone\", List.of(ItemStack.of(Material.GILDED_BLACKSTONE, 1)));\n        expected.put(\"glass\", List.of());\n        expected.put(\"glass_pane\", List.of());\n        expected.put(\"glow_lichen\", List.of());\n        expected.put(\"glowstone\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"gold_block\", List.of(ItemStack.of(Material.GOLD_BLOCK, 1)));\n        expected.put(\"gold_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"granite\", List.of(ItemStack.of(Material.GRANITE, 1)));\n        expected.put(\"granite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"granite_stairs\", List.of(ItemStack.of(Material.GRANITE_STAIRS, 1)));\n        expected.put(\"granite_wall\", List.of(ItemStack.of(Material.GRANITE_WALL, 1)));\n        expected.put(\"grass_block\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"gravel\", List.of(ItemStack.of(Material.GRAVEL, 1)));\n        expected.put(\"gray_banner\", List.of(ItemStack.of(Material.GRAY_BANNER, 1)));\n        expected.put(\"gray_bed\", List.of());\n        expected.put(\"gray_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"gray_candle_cake\", List.of(ItemStack.of(Material.GRAY_CANDLE, 1)));\n        expected.put(\"gray_carpet\", List.of(ItemStack.of(Material.GRAY_CARPET, 1)));\n        expected.put(\"gray_concrete\", List.of(ItemStack.of(Material.GRAY_CONCRETE, 1)));\n        expected.put(\"gray_concrete_powder\", List.of(ItemStack.of(Material.GRAY_CONCRETE_POWDER, 1)));\n        expected.put(\"gray_glazed_terracotta\", List.of(ItemStack.of(Material.GRAY_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"gray_shulker_box\", List.of(ItemStack.of(Material.GRAY_SHULKER_BOX, 1)));\n        expected.put(\"gray_stained_glass\", List.of());\n        expected.put(\"gray_stained_glass_pane\", List.of());\n        expected.put(\"gray_terracotta\", List.of(ItemStack.of(Material.GRAY_TERRACOTTA, 1)));\n        expected.put(\"gray_wool\", List.of(ItemStack.of(Material.GRAY_WOOL, 1)));\n        expected.put(\"green_banner\", List.of(ItemStack.of(Material.GREEN_BANNER, 1)));\n        expected.put(\"green_bed\", List.of());\n        expected.put(\"green_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"green_candle_cake\", List.of(ItemStack.of(Material.GREEN_CANDLE, 1)));\n        expected.put(\"green_carpet\", List.of(ItemStack.of(Material.GREEN_CARPET, 1)));\n        expected.put(\"green_concrete\", List.of(ItemStack.of(Material.GREEN_CONCRETE, 1)));\n        expected.put(\"green_concrete_powder\", List.of(ItemStack.of(Material.GREEN_CONCRETE_POWDER, 1)));\n        expected.put(\"green_glazed_terracotta\", List.of(ItemStack.of(Material.GREEN_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"green_shulker_box\", List.of(ItemStack.of(Material.GREEN_SHULKER_BOX, 1)));\n        expected.put(\"green_stained_glass\", List.of());\n        expected.put(\"green_stained_glass_pane\", List.of());\n        expected.put(\"green_terracotta\", List.of(ItemStack.of(Material.GREEN_TERRACOTTA, 1)));\n        expected.put(\"green_wool\", List.of(ItemStack.of(Material.GREEN_WOOL, 1)));\n        expected.put(\"grindstone\", List.of(ItemStack.of(Material.GRINDSTONE, 1)));\n        expected.put(\"hanging_roots\", List.of());\n        expected.put(\"hay_block\", List.of(ItemStack.of(Material.HAY_BLOCK, 1)));\n        expected.put(\"heavy_weighted_pressure_plate\", List.of(ItemStack.of(Material.HEAVY_WEIGHTED_PRESSURE_PLATE, 1)));\n        expected.put(\"honey_block\", List.of(ItemStack.of(Material.HONEY_BLOCK, 1)));\n        expected.put(\"honeycomb_block\", List.of(ItemStack.of(Material.HONEYCOMB_BLOCK, 1)));\n        expected.put(\"hopper\", List.of(ItemStack.of(Material.HOPPER, 1)));\n        expected.put(\"horn_coral\", List.of());\n        expected.put(\"horn_coral_block\", List.of(ItemStack.of(Material.DEAD_HORN_CORAL_BLOCK, 1)));\n        expected.put(\"horn_coral_fan\", List.of());\n        expected.put(\"ice\", List.of());\n        expected.put(\"infested_chiseled_stone_bricks\", List.of());\n        expected.put(\"infested_cobblestone\", List.of());\n        expected.put(\"infested_cracked_stone_bricks\", List.of());\n        expected.put(\"infested_deepslate\", List.of());\n        expected.put(\"infested_mossy_stone_bricks\", List.of());\n        expected.put(\"infested_stone\", List.of());\n        expected.put(\"infested_stone_bricks\", List.of());\n        expected.put(\"iron_bars\", List.of(ItemStack.of(Material.IRON_BARS, 1)));\n        expected.put(\"iron_block\", List.of(ItemStack.of(Material.IRON_BLOCK, 1)));\n        expected.put(\"iron_door\", List.of());\n        expected.put(\"iron_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"iron_trapdoor\", List.of(ItemStack.of(Material.IRON_TRAPDOOR, 1)));\n        expected.put(\"jack_o_lantern\", List.of(ItemStack.of(Material.JACK_O_LANTERN, 1)));\n        expected.put(\"jukebox\", List.of(ItemStack.of(Material.JUKEBOX, 1)));\n        expected.put(\"jungle_button\", List.of(ItemStack.of(Material.JUNGLE_BUTTON, 1)));\n        expected.put(\"jungle_door\", List.of());\n        expected.put(\"jungle_fence\", List.of(ItemStack.of(Material.JUNGLE_FENCE, 1)));\n        expected.put(\"jungle_fence_gate\", List.of(ItemStack.of(Material.JUNGLE_FENCE_GATE, 1)));\n        expected.put(\"jungle_hanging_sign\", List.of(ItemStack.of(Material.JUNGLE_HANGING_SIGN, 1)));\n        expected.put(\"jungle_leaves\", List.of());\n        expected.put(\"jungle_log\", List.of(ItemStack.of(Material.JUNGLE_LOG, 1)));\n        expected.put(\"jungle_planks\", List.of(ItemStack.of(Material.JUNGLE_PLANKS, 1)));\n        expected.put(\"jungle_pressure_plate\", List.of(ItemStack.of(Material.JUNGLE_PRESSURE_PLATE, 1)));\n        expected.put(\"jungle_sapling\", List.of(ItemStack.of(Material.JUNGLE_SAPLING, 1)));\n        expected.put(\"jungle_sign\", List.of(ItemStack.of(Material.JUNGLE_SIGN, 1)));\n        expected.put(\"jungle_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"jungle_stairs\", List.of(ItemStack.of(Material.JUNGLE_STAIRS, 1)));\n        expected.put(\"jungle_trapdoor\", List.of(ItemStack.of(Material.JUNGLE_TRAPDOOR, 1)));\n        expected.put(\"jungle_wood\", List.of(ItemStack.of(Material.JUNGLE_WOOD, 1)));\n        expected.put(\"kelp\", List.of(ItemStack.of(Material.KELP, 1)));\n        expected.put(\"kelp_plant\", List.of(ItemStack.of(Material.KELP, 1)));\n        expected.put(\"ladder\", List.of(ItemStack.of(Material.LADDER, 1)));\n        expected.put(\"lantern\", List.of(ItemStack.of(Material.LANTERN, 1)));\n        expected.put(\"lapis_block\", List.of(ItemStack.of(Material.LAPIS_BLOCK, 1)));\n        expected.put(\"lapis_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"large_amethyst_bud\", List.of());\n        expected.put(\"large_fern\", List.of());\n        expected.put(\"lava_cauldron\", List.of(ItemStack.of(Material.CAULDRON, 1)));\n        expected.put(\"lectern\", List.of(ItemStack.of(Material.LECTERN, 1)));\n        expected.put(\"lever\", List.of(ItemStack.of(Material.LEVER, 1)));\n        expected.put(\"light_blue_banner\", List.of(ItemStack.of(Material.LIGHT_BLUE_BANNER, 1)));\n        expected.put(\"light_blue_bed\", List.of());\n        expected.put(\"light_blue_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"light_blue_candle_cake\", List.of(ItemStack.of(Material.LIGHT_BLUE_CANDLE, 1)));\n        expected.put(\"light_blue_carpet\", List.of(ItemStack.of(Material.LIGHT_BLUE_CARPET, 1)));\n        expected.put(\"light_blue_concrete\", List.of(ItemStack.of(Material.LIGHT_BLUE_CONCRETE, 1)));\n        expected.put(\"light_blue_concrete_powder\", List.of(ItemStack.of(Material.LIGHT_BLUE_CONCRETE_POWDER, 1)));\n        expected.put(\"light_blue_glazed_terracotta\", List.of(ItemStack.of(Material.LIGHT_BLUE_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"light_blue_shulker_box\", List.of(ItemStack.of(Material.LIGHT_BLUE_SHULKER_BOX, 1)));\n        expected.put(\"light_blue_stained_glass\", List.of());\n        expected.put(\"light_blue_stained_glass_pane\", List.of());\n        expected.put(\"light_blue_terracotta\", List.of(ItemStack.of(Material.LIGHT_BLUE_TERRACOTTA, 1)));\n        expected.put(\"light_blue_wool\", List.of(ItemStack.of(Material.LIGHT_BLUE_WOOL, 1)));\n        expected.put(\"light_gray_banner\", List.of(ItemStack.of(Material.LIGHT_GRAY_BANNER, 1)));\n        expected.put(\"light_gray_bed\", List.of());\n        expected.put(\"light_gray_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"light_gray_candle_cake\", List.of(ItemStack.of(Material.LIGHT_GRAY_CANDLE, 1)));\n        expected.put(\"light_gray_carpet\", List.of(ItemStack.of(Material.LIGHT_GRAY_CARPET, 1)));\n        expected.put(\"light_gray_concrete\", List.of(ItemStack.of(Material.LIGHT_GRAY_CONCRETE, 1)));\n        expected.put(\"light_gray_concrete_powder\", List.of(ItemStack.of(Material.LIGHT_GRAY_CONCRETE_POWDER, 1)));\n        expected.put(\"light_gray_glazed_terracotta\", List.of(ItemStack.of(Material.LIGHT_GRAY_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"light_gray_shulker_box\", List.of(ItemStack.of(Material.LIGHT_GRAY_SHULKER_BOX, 1)));\n        expected.put(\"light_gray_stained_glass\", List.of());\n        expected.put(\"light_gray_stained_glass_pane\", List.of());\n        expected.put(\"light_gray_terracotta\", List.of(ItemStack.of(Material.LIGHT_GRAY_TERRACOTTA, 1)));\n        expected.put(\"light_gray_wool\", List.of(ItemStack.of(Material.LIGHT_GRAY_WOOL, 1)));\n        expected.put(\"light_weighted_pressure_plate\", List.of(ItemStack.of(Material.LIGHT_WEIGHTED_PRESSURE_PLATE, 1)));\n        expected.put(\"lightning_rod\", List.of(ItemStack.of(Material.LIGHTNING_ROD, 1)));\n        expected.put(\"lilac\", List.of());\n        expected.put(\"lily_of_the_valley\", List.of(ItemStack.of(Material.LILY_OF_THE_VALLEY, 1)));\n        expected.put(\"lily_pad\", List.of(ItemStack.of(Material.LILY_PAD, 1)));\n        expected.put(\"lime_banner\", List.of(ItemStack.of(Material.LIME_BANNER, 1)));\n        expected.put(\"lime_bed\", List.of());\n        expected.put(\"lime_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"lime_candle_cake\", List.of(ItemStack.of(Material.LIME_CANDLE, 1)));\n        expected.put(\"lime_carpet\", List.of(ItemStack.of(Material.LIME_CARPET, 1)));\n        expected.put(\"lime_concrete\", List.of(ItemStack.of(Material.LIME_CONCRETE, 1)));\n        expected.put(\"lime_concrete_powder\", List.of(ItemStack.of(Material.LIME_CONCRETE_POWDER, 1)));\n        expected.put(\"lime_glazed_terracotta\", List.of(ItemStack.of(Material.LIME_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"lime_shulker_box\", List.of(ItemStack.of(Material.LIME_SHULKER_BOX, 1)));\n        expected.put(\"lime_stained_glass\", List.of());\n        expected.put(\"lime_stained_glass_pane\", List.of());\n        expected.put(\"lime_terracotta\", List.of(ItemStack.of(Material.LIME_TERRACOTTA, 1)));\n        expected.put(\"lime_wool\", List.of(ItemStack.of(Material.LIME_WOOL, 1)));\n        expected.put(\"lodestone\", List.of(ItemStack.of(Material.LODESTONE, 1)));\n        expected.put(\"loom\", List.of(ItemStack.of(Material.LOOM, 1)));\n        expected.put(\"magenta_banner\", List.of(ItemStack.of(Material.MAGENTA_BANNER, 1)));\n        expected.put(\"magenta_bed\", List.of());\n        expected.put(\"magenta_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"magenta_candle_cake\", List.of(ItemStack.of(Material.MAGENTA_CANDLE, 1)));\n        expected.put(\"magenta_carpet\", List.of(ItemStack.of(Material.MAGENTA_CARPET, 1)));\n        expected.put(\"magenta_concrete\", List.of(ItemStack.of(Material.MAGENTA_CONCRETE, 1)));\n        expected.put(\"magenta_concrete_powder\", List.of(ItemStack.of(Material.MAGENTA_CONCRETE_POWDER, 1)));\n        expected.put(\"magenta_glazed_terracotta\", List.of(ItemStack.of(Material.MAGENTA_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"magenta_shulker_box\", List.of(ItemStack.of(Material.MAGENTA_SHULKER_BOX, 1)));\n        expected.put(\"magenta_stained_glass\", List.of());\n        expected.put(\"magenta_stained_glass_pane\", List.of());\n        expected.put(\"magenta_terracotta\", List.of(ItemStack.of(Material.MAGENTA_TERRACOTTA, 1)));\n        expected.put(\"magenta_wool\", List.of(ItemStack.of(Material.MAGENTA_WOOL, 1)));\n        expected.put(\"magma_block\", List.of(ItemStack.of(Material.MAGMA_BLOCK, 1)));\n        expected.put(\"mangrove_button\", List.of(ItemStack.of(Material.MANGROVE_BUTTON, 1)));\n        expected.put(\"mangrove_door\", List.of());\n        expected.put(\"mangrove_fence\", List.of(ItemStack.of(Material.MANGROVE_FENCE, 1)));\n        expected.put(\"mangrove_fence_gate\", List.of(ItemStack.of(Material.MANGROVE_FENCE_GATE, 1)));\n        expected.put(\"mangrove_hanging_sign\", List.of(ItemStack.of(Material.MANGROVE_HANGING_SIGN, 1)));\n        expected.put(\"mangrove_leaves\", List.of());\n        expected.put(\"mangrove_log\", List.of(ItemStack.of(Material.MANGROVE_LOG, 1)));\n        expected.put(\"mangrove_planks\", List.of(ItemStack.of(Material.MANGROVE_PLANKS, 1)));\n        expected.put(\"mangrove_pressure_plate\", List.of(ItemStack.of(Material.MANGROVE_PRESSURE_PLATE, 1)));\n        expected.put(\"mangrove_propagule\", List.of());\n        expected.put(\"mangrove_roots\", List.of(ItemStack.of(Material.MANGROVE_ROOTS, 1)));\n        expected.put(\"mangrove_sign\", List.of(ItemStack.of(Material.MANGROVE_SIGN, 1)));\n        expected.put(\"mangrove_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"mangrove_stairs\", List.of(ItemStack.of(Material.MANGROVE_STAIRS, 1)));\n        expected.put(\"mangrove_trapdoor\", List.of(ItemStack.of(Material.MANGROVE_TRAPDOOR, 1)));\n        expected.put(\"mangrove_wood\", List.of(ItemStack.of(Material.MANGROVE_WOOD, 1)));\n        expected.put(\"medium_amethyst_bud\", List.of());\n        expected.put(\"melon\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"melon_stem\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"moss_block\", List.of(ItemStack.of(Material.MOSS_BLOCK, 1)));\n        expected.put(\"moss_carpet\", List.of(ItemStack.of(Material.MOSS_CARPET, 1)));\n        expected.put(\"mossy_cobblestone\", List.of(ItemStack.of(Material.MOSSY_COBBLESTONE, 1)));\n        expected.put(\"mossy_cobblestone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"mossy_cobblestone_stairs\", List.of(ItemStack.of(Material.MOSSY_COBBLESTONE_STAIRS, 1)));\n        expected.put(\"mossy_cobblestone_wall\", List.of(ItemStack.of(Material.MOSSY_COBBLESTONE_WALL, 1)));\n        expected.put(\"mossy_stone_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"mossy_stone_brick_stairs\", List.of(ItemStack.of(Material.MOSSY_STONE_BRICK_STAIRS, 1)));\n        expected.put(\"mossy_stone_brick_wall\", List.of(ItemStack.of(Material.MOSSY_STONE_BRICK_WALL, 1)));\n        expected.put(\"mossy_stone_bricks\", List.of(ItemStack.of(Material.MOSSY_STONE_BRICKS, 1)));\n        expected.put(\"mud\", List.of(ItemStack.of(Material.MUD, 1)));\n        expected.put(\"mud_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"mud_brick_stairs\", List.of(ItemStack.of(Material.MUD_BRICK_STAIRS, 1)));\n        expected.put(\"mud_brick_wall\", List.of(ItemStack.of(Material.MUD_BRICK_WALL, 1)));\n        expected.put(\"mud_bricks\", List.of(ItemStack.of(Material.MUD_BRICKS, 1)));\n        expected.put(\"muddy_mangrove_roots\", List.of(ItemStack.of(Material.MUDDY_MANGROVE_ROOTS, 1)));\n        expected.put(\"mushroom_stem\", List.of());\n        expected.put(\"mycelium\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"nether_brick_fence\", List.of(ItemStack.of(Material.NETHER_BRICK_FENCE, 1)));\n        expected.put(\"nether_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"nether_brick_stairs\", List.of(ItemStack.of(Material.NETHER_BRICK_STAIRS, 1)));\n        expected.put(\"nether_brick_wall\", List.of(ItemStack.of(Material.NETHER_BRICK_WALL, 1)));\n        expected.put(\"nether_bricks\", List.of(ItemStack.of(Material.NETHER_BRICKS, 1)));\n        expected.put(\"nether_gold_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"nether_portal\", List.of());\n        expected.put(\"nether_quartz_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"nether_sprouts\", List.of());\n        expected.put(\"nether_wart\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"nether_wart_block\", List.of(ItemStack.of(Material.NETHER_WART_BLOCK, 1)));\n        expected.put(\"netherite_block\", List.of(ItemStack.of(Material.NETHERITE_BLOCK, 1)));\n        expected.put(\"netherrack\", List.of(ItemStack.of(Material.NETHERRACK, 1)));\n        expected.put(\"note_block\", List.of(ItemStack.of(Material.NOTE_BLOCK, 1)));\n        expected.put(\"oak_button\", List.of(ItemStack.of(Material.OAK_BUTTON, 1)));\n        expected.put(\"oak_door\", List.of());\n        expected.put(\"oak_fence\", List.of(ItemStack.of(Material.OAK_FENCE, 1)));\n        expected.put(\"oak_fence_gate\", List.of(ItemStack.of(Material.OAK_FENCE_GATE, 1)));\n        expected.put(\"oak_hanging_sign\", List.of(ItemStack.of(Material.OAK_HANGING_SIGN, 1)));\n        expected.put(\"oak_leaves\", List.of());\n        expected.put(\"oak_log\", List.of(ItemStack.of(Material.OAK_LOG, 1)));\n        expected.put(\"oak_planks\", List.of(ItemStack.of(Material.OAK_PLANKS, 1)));\n        expected.put(\"oak_pressure_plate\", List.of(ItemStack.of(Material.OAK_PRESSURE_PLATE, 1)));\n        expected.put(\"oak_sapling\", List.of(ItemStack.of(Material.OAK_SAPLING, 1)));\n        expected.put(\"oak_sign\", List.of(ItemStack.of(Material.OAK_SIGN, 1)));\n        expected.put(\"oak_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"oak_stairs\", List.of(ItemStack.of(Material.OAK_STAIRS, 1)));\n        expected.put(\"oak_trapdoor\", List.of(ItemStack.of(Material.OAK_TRAPDOOR, 1)));\n        expected.put(\"oak_wood\", List.of(ItemStack.of(Material.OAK_WOOD, 1)));\n        expected.put(\"observer\", List.of(ItemStack.of(Material.OBSERVER, 1)));\n        expected.put(\"obsidian\", List.of(ItemStack.of(Material.OBSIDIAN, 1)));\n        expected.put(\"ochre_froglight\", List.of(ItemStack.of(Material.OCHRE_FROGLIGHT, 1)));\n        expected.put(\"orange_banner\", List.of(ItemStack.of(Material.ORANGE_BANNER, 1)));\n        expected.put(\"orange_bed\", List.of());\n        expected.put(\"orange_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"orange_candle_cake\", List.of(ItemStack.of(Material.ORANGE_CANDLE, 1)));\n        expected.put(\"orange_carpet\", List.of(ItemStack.of(Material.ORANGE_CARPET, 1)));\n        expected.put(\"orange_concrete\", List.of(ItemStack.of(Material.ORANGE_CONCRETE, 1)));\n        expected.put(\"orange_concrete_powder\", List.of(ItemStack.of(Material.ORANGE_CONCRETE_POWDER, 1)));\n        expected.put(\"orange_glazed_terracotta\", List.of(ItemStack.of(Material.ORANGE_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"orange_shulker_box\", List.of(ItemStack.of(Material.ORANGE_SHULKER_BOX, 1)));\n        expected.put(\"orange_stained_glass\", List.of());\n        expected.put(\"orange_stained_glass_pane\", List.of());\n        expected.put(\"orange_terracotta\", List.of(ItemStack.of(Material.ORANGE_TERRACOTTA, 1)));\n        expected.put(\"orange_tulip\", List.of(ItemStack.of(Material.ORANGE_TULIP, 1)));\n        expected.put(\"orange_wool\", List.of(ItemStack.of(Material.ORANGE_WOOL, 1)));\n        expected.put(\"oxeye_daisy\", List.of(ItemStack.of(Material.OXEYE_DAISY, 1)));\n        expected.put(\"oxidized_chiseled_copper\", List.of());\n        expected.put(\"oxidized_copper\", List.of(ItemStack.of(Material.OXIDIZED_COPPER, 1)));\n        expected.put(\"oxidized_copper_bulb\", List.of());\n        expected.put(\"oxidized_copper_door\", List.of());\n        expected.put(\"oxidized_copper_grate\", List.of());\n        expected.put(\"oxidized_copper_trapdoor\", List.of());\n        expected.put(\"oxidized_cut_copper\", List.of(ItemStack.of(Material.OXIDIZED_CUT_COPPER, 1)));\n        expected.put(\"oxidized_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"oxidized_cut_copper_stairs\", List.of(ItemStack.of(Material.OXIDIZED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"packed_ice\", List.of());\n        expected.put(\"packed_mud\", List.of(ItemStack.of(Material.PACKED_MUD, 1)));\n        expected.put(\"pearlescent_froglight\", List.of(ItemStack.of(Material.PEARLESCENT_FROGLIGHT, 1)));\n        expected.put(\"peony\", List.of());\n        expected.put(\"petrified_oak_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"piglin_head\", List.of(ItemStack.of(Material.PIGLIN_HEAD, 1)));\n        expected.put(\"pink_banner\", List.of(ItemStack.of(Material.PINK_BANNER, 1)));\n        expected.put(\"pink_bed\", List.of());\n        expected.put(\"pink_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"pink_candle_cake\", List.of(ItemStack.of(Material.PINK_CANDLE, 1)));\n        expected.put(\"pink_carpet\", List.of(ItemStack.of(Material.PINK_CARPET, 1)));\n        expected.put(\"pink_concrete\", List.of(ItemStack.of(Material.PINK_CONCRETE, 1)));\n        expected.put(\"pink_concrete_powder\", List.of(ItemStack.of(Material.PINK_CONCRETE_POWDER, 1)));\n        expected.put(\"pink_glazed_terracotta\", List.of(ItemStack.of(Material.PINK_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"pink_petals\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"pink_shulker_box\", List.of(ItemStack.of(Material.PINK_SHULKER_BOX, 1)));\n        expected.put(\"pink_stained_glass\", List.of());\n        expected.put(\"pink_stained_glass_pane\", List.of());\n        expected.put(\"pink_terracotta\", List.of(ItemStack.of(Material.PINK_TERRACOTTA, 1)));\n        expected.put(\"pink_tulip\", List.of(ItemStack.of(Material.PINK_TULIP, 1)));\n        expected.put(\"pink_wool\", List.of(ItemStack.of(Material.PINK_WOOL, 1)));\n        expected.put(\"piston\", List.of(ItemStack.of(Material.PISTON, 1)));\n        expected.put(\"pitcher_crop\", List.of());\n        expected.put(\"pitcher_plant\", List.of());\n        expected.put(\"player_head\", List.of(ItemStack.of(Material.PLAYER_HEAD, 1)));\n        expected.put(\"podzol\", List.of(ItemStack.of(Material.DIRT, 1)));\n        expected.put(\"pointed_dripstone\", List.of(ItemStack.of(Material.POINTED_DRIPSTONE, 1)));\n        expected.put(\"polished_andesite\", List.of(ItemStack.of(Material.POLISHED_ANDESITE, 1)));\n        expected.put(\"polished_andesite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_andesite_stairs\", List.of(ItemStack.of(Material.POLISHED_ANDESITE_STAIRS, 1)));\n        expected.put(\"polished_basalt\", List.of(ItemStack.of(Material.POLISHED_BASALT, 1)));\n        expected.put(\"polished_blackstone\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE, 1)));\n        expected.put(\"polished_blackstone_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_blackstone_brick_stairs\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_BRICK_STAIRS, 1)));\n        expected.put(\"polished_blackstone_brick_wall\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_BRICK_WALL, 1)));\n        expected.put(\"polished_blackstone_bricks\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_BRICKS, 1)));\n        expected.put(\"polished_blackstone_button\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_BUTTON, 1)));\n        expected.put(\"polished_blackstone_pressure_plate\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_PRESSURE_PLATE, 1)));\n        expected.put(\"polished_blackstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_blackstone_stairs\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_STAIRS, 1)));\n        expected.put(\"polished_blackstone_wall\", List.of(ItemStack.of(Material.POLISHED_BLACKSTONE_WALL, 1)));\n        expected.put(\"polished_deepslate\", List.of(ItemStack.of(Material.POLISHED_DEEPSLATE, 1)));\n        expected.put(\"polished_deepslate_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_deepslate_stairs\", List.of(ItemStack.of(Material.POLISHED_DEEPSLATE_STAIRS, 1)));\n        expected.put(\"polished_deepslate_wall\", List.of(ItemStack.of(Material.POLISHED_DEEPSLATE_WALL, 1)));\n        expected.put(\"polished_diorite\", List.of(ItemStack.of(Material.POLISHED_DIORITE, 1)));\n        expected.put(\"polished_diorite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_diorite_stairs\", List.of(ItemStack.of(Material.POLISHED_DIORITE_STAIRS, 1)));\n        expected.put(\"polished_granite\", List.of(ItemStack.of(Material.POLISHED_GRANITE, 1)));\n        expected.put(\"polished_granite_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"polished_granite_stairs\", List.of(ItemStack.of(Material.POLISHED_GRANITE_STAIRS, 1)));\n        expected.put(\"polished_tuff\", List.of());\n        expected.put(\"polished_tuff_slab\", List.of());\n        expected.put(\"polished_tuff_stairs\", List.of());\n        expected.put(\"polished_tuff_wall\", List.of());\n        expected.put(\"poppy\", List.of(ItemStack.of(Material.POPPY, 1)));\n        expected.put(\"potatoes\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"potted_acacia_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.ACACIA_SAPLING, 1)));\n        expected.put(\"potted_allium\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.ALLIUM, 1)));\n        expected.put(\"potted_azalea_bush\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.AZALEA, 1)));\n        expected.put(\"potted_azure_bluet\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.AZURE_BLUET, 1)));\n        expected.put(\"potted_bamboo\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.BAMBOO, 1)));\n        expected.put(\"potted_birch_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.BIRCH_SAPLING, 1)));\n        expected.put(\"potted_blue_orchid\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.BLUE_ORCHID, 1)));\n        expected.put(\"potted_brown_mushroom\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.BROWN_MUSHROOM, 1)));\n        expected.put(\"potted_cactus\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.CACTUS, 1)));\n        expected.put(\"potted_cherry_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.CHERRY_SAPLING, 1)));\n        expected.put(\"potted_cornflower\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.CORNFLOWER, 1)));\n        expected.put(\"potted_crimson_fungus\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.CRIMSON_FUNGUS, 1)));\n        expected.put(\"potted_crimson_roots\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.CRIMSON_ROOTS, 1)));\n        expected.put(\"potted_dandelion\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.DANDELION, 1)));\n        expected.put(\"potted_dark_oak_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.DARK_OAK_SAPLING, 1)));\n        expected.put(\"potted_dead_bush\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.DEAD_BUSH, 1)));\n        expected.put(\"potted_fern\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.FERN, 1)));\n        expected.put(\"potted_flowering_azalea_bush\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.FLOWERING_AZALEA, 1)));\n        expected.put(\"potted_jungle_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.JUNGLE_SAPLING, 1)));\n        expected.put(\"potted_lily_of_the_valley\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.LILY_OF_THE_VALLEY, 1)));\n        expected.put(\"potted_mangrove_propagule\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.MANGROVE_PROPAGULE, 1)));\n        expected.put(\"potted_oak_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.OAK_SAPLING, 1)));\n        expected.put(\"potted_orange_tulip\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.ORANGE_TULIP, 1)));\n        expected.put(\"potted_oxeye_daisy\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.OXEYE_DAISY, 1)));\n        expected.put(\"potted_pink_tulip\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.PINK_TULIP, 1)));\n        expected.put(\"potted_poppy\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.POPPY, 1)));\n        expected.put(\"potted_red_mushroom\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.RED_MUSHROOM, 1)));\n        expected.put(\"potted_red_tulip\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.RED_TULIP, 1)));\n        expected.put(\"potted_spruce_sapling\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.SPRUCE_SAPLING, 1)));\n        expected.put(\"potted_torchflower\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.TORCHFLOWER, 1)));\n        expected.put(\"potted_warped_fungus\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.WARPED_FUNGUS, 1)));\n        expected.put(\"potted_warped_roots\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.WARPED_ROOTS, 1)));\n        expected.put(\"potted_white_tulip\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.WHITE_TULIP, 1)));\n        expected.put(\"potted_wither_rose\", List.of(ItemStack.of(Material.FLOWER_POT, 1), ItemStack.of(Material.WITHER_ROSE, 1)));\n        expected.put(\"powder_snow\", List.of());\n        expected.put(\"powder_snow_cauldron\", List.of(ItemStack.of(Material.CAULDRON, 1)));\n        expected.put(\"powered_rail\", List.of(ItemStack.of(Material.POWERED_RAIL, 1)));\n        expected.put(\"prismarine\", List.of(ItemStack.of(Material.PRISMARINE, 1)));\n        expected.put(\"prismarine_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"prismarine_brick_stairs\", List.of(ItemStack.of(Material.PRISMARINE_BRICK_STAIRS, 1)));\n        expected.put(\"prismarine_bricks\", List.of(ItemStack.of(Material.PRISMARINE_BRICKS, 1)));\n        expected.put(\"prismarine_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"prismarine_stairs\", List.of(ItemStack.of(Material.PRISMARINE_STAIRS, 1)));\n        expected.put(\"prismarine_wall\", List.of(ItemStack.of(Material.PRISMARINE_WALL, 1)));\n        expected.put(\"pumpkin\", List.of(ItemStack.of(Material.PUMPKIN, 1)));\n        expected.put(\"pumpkin_stem\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"purple_banner\", List.of(ItemStack.of(Material.PURPLE_BANNER, 1)));\n        expected.put(\"purple_bed\", List.of());\n        expected.put(\"purple_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"purple_candle_cake\", List.of(ItemStack.of(Material.PURPLE_CANDLE, 1)));\n        expected.put(\"purple_carpet\", List.of(ItemStack.of(Material.PURPLE_CARPET, 1)));\n        expected.put(\"purple_concrete\", List.of(ItemStack.of(Material.PURPLE_CONCRETE, 1)));\n        expected.put(\"purple_concrete_powder\", List.of(ItemStack.of(Material.PURPLE_CONCRETE_POWDER, 1)));\n        expected.put(\"purple_glazed_terracotta\", List.of(ItemStack.of(Material.PURPLE_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"purple_shulker_box\", List.of(ItemStack.of(Material.PURPLE_SHULKER_BOX, 1)));\n        expected.put(\"purple_stained_glass\", List.of());\n        expected.put(\"purple_stained_glass_pane\", List.of());\n        expected.put(\"purple_terracotta\", List.of(ItemStack.of(Material.PURPLE_TERRACOTTA, 1)));\n        expected.put(\"purple_wool\", List.of(ItemStack.of(Material.PURPLE_WOOL, 1)));\n        expected.put(\"purpur_block\", List.of(ItemStack.of(Material.PURPUR_BLOCK, 1)));\n        expected.put(\"purpur_pillar\", List.of(ItemStack.of(Material.PURPUR_PILLAR, 1)));\n        expected.put(\"purpur_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"purpur_stairs\", List.of(ItemStack.of(Material.PURPUR_STAIRS, 1)));\n        expected.put(\"quartz_block\", List.of(ItemStack.of(Material.QUARTZ_BLOCK, 1)));\n        expected.put(\"quartz_bricks\", List.of(ItemStack.of(Material.QUARTZ_BRICKS, 1)));\n        expected.put(\"quartz_pillar\", List.of(ItemStack.of(Material.QUARTZ_PILLAR, 1)));\n        expected.put(\"quartz_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"quartz_stairs\", List.of(ItemStack.of(Material.QUARTZ_STAIRS, 1)));\n        expected.put(\"rail\", List.of(ItemStack.of(Material.RAIL, 1)));\n        expected.put(\"raw_copper_block\", List.of(ItemStack.of(Material.RAW_COPPER_BLOCK, 1)));\n        expected.put(\"raw_gold_block\", List.of(ItemStack.of(Material.RAW_GOLD_BLOCK, 1)));\n        expected.put(\"raw_iron_block\", List.of(ItemStack.of(Material.RAW_IRON_BLOCK, 1)));\n        expected.put(\"red_banner\", List.of(ItemStack.of(Material.RED_BANNER, 1)));\n        expected.put(\"red_bed\", List.of());\n        expected.put(\"red_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"red_candle_cake\", List.of(ItemStack.of(Material.RED_CANDLE, 1)));\n        expected.put(\"red_carpet\", List.of(ItemStack.of(Material.RED_CARPET, 1)));\n        expected.put(\"red_concrete\", List.of(ItemStack.of(Material.RED_CONCRETE, 1)));\n        expected.put(\"red_concrete_powder\", List.of(ItemStack.of(Material.RED_CONCRETE_POWDER, 1)));\n        expected.put(\"red_glazed_terracotta\", List.of(ItemStack.of(Material.RED_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"red_mushroom\", List.of(ItemStack.of(Material.RED_MUSHROOM, 1)));\n        expected.put(\"red_mushroom_block\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"red_nether_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"red_nether_brick_stairs\", List.of(ItemStack.of(Material.RED_NETHER_BRICK_STAIRS, 1)));\n        expected.put(\"red_nether_brick_wall\", List.of(ItemStack.of(Material.RED_NETHER_BRICK_WALL, 1)));\n        expected.put(\"red_nether_bricks\", List.of(ItemStack.of(Material.RED_NETHER_BRICKS, 1)));\n        expected.put(\"red_sand\", List.of(ItemStack.of(Material.RED_SAND, 1)));\n        expected.put(\"red_sandstone\", List.of(ItemStack.of(Material.RED_SANDSTONE, 1)));\n        expected.put(\"red_sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"red_sandstone_stairs\", List.of(ItemStack.of(Material.RED_SANDSTONE_STAIRS, 1)));\n        expected.put(\"red_sandstone_wall\", List.of(ItemStack.of(Material.RED_SANDSTONE_WALL, 1)));\n        expected.put(\"red_shulker_box\", List.of(ItemStack.of(Material.RED_SHULKER_BOX, 1)));\n        expected.put(\"red_stained_glass\", List.of());\n        expected.put(\"red_stained_glass_pane\", List.of());\n        expected.put(\"red_terracotta\", List.of(ItemStack.of(Material.RED_TERRACOTTA, 1)));\n        expected.put(\"red_tulip\", List.of(ItemStack.of(Material.RED_TULIP, 1)));\n        expected.put(\"red_wool\", List.of(ItemStack.of(Material.RED_WOOL, 1)));\n        expected.put(\"redstone_block\", List.of(ItemStack.of(Material.REDSTONE_BLOCK, 1)));\n        expected.put(\"redstone_lamp\", List.of(ItemStack.of(Material.REDSTONE_LAMP, 1)));\n        expected.put(\"redstone_ore\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"redstone_torch\", List.of(ItemStack.of(Material.REDSTONE_TORCH, 1)));\n        expected.put(\"redstone_wire\", List.of(ItemStack.of(Material.REDSTONE, 1)));\n        expected.put(\"reinforced_deepslate\", List.of());\n        expected.put(\"repeater\", List.of(ItemStack.of(Material.REPEATER, 1)));\n        expected.put(\"respawn_anchor\", List.of(ItemStack.of(Material.RESPAWN_ANCHOR, 1)));\n        expected.put(\"rooted_dirt\", List.of(ItemStack.of(Material.ROOTED_DIRT, 1)));\n        expected.put(\"rose_bush\", List.of());\n        expected.put(\"sand\", List.of(ItemStack.of(Material.SAND, 1)));\n        expected.put(\"sandstone\", List.of(ItemStack.of(Material.SANDSTONE, 1)));\n        expected.put(\"sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"sandstone_stairs\", List.of(ItemStack.of(Material.SANDSTONE_STAIRS, 1)));\n        expected.put(\"sandstone_wall\", List.of(ItemStack.of(Material.SANDSTONE_WALL, 1)));\n        expected.put(\"scaffolding\", List.of(ItemStack.of(Material.SCAFFOLDING, 1)));\n        expected.put(\"sculk\", List.of());\n        expected.put(\"sculk_catalyst\", List.of());\n        expected.put(\"sculk_sensor\", List.of());\n        expected.put(\"sculk_shrieker\", List.of());\n        expected.put(\"sculk_vein\", List.of());\n        expected.put(\"sea_lantern\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"sea_pickle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"seagrass\", List.of());\n        expected.put(\"short_grass\", List.of());\n        expected.put(\"shroomlight\", List.of(ItemStack.of(Material.SHROOMLIGHT, 1)));\n        expected.put(\"shulker_box\", List.of(ItemStack.of(Material.SHULKER_BOX, 1)));\n        expected.put(\"skeleton_skull\", List.of(ItemStack.of(Material.SKELETON_SKULL, 1)));\n        expected.put(\"slime_block\", List.of(ItemStack.of(Material.SLIME_BLOCK, 1)));\n        expected.put(\"small_amethyst_bud\", List.of());\n        expected.put(\"small_dripleaf\", List.of());\n        expected.put(\"smithing_table\", List.of(ItemStack.of(Material.SMITHING_TABLE, 1)));\n        expected.put(\"smoker\", List.of(ItemStack.of(Material.SMOKER, 1)));\n        expected.put(\"smooth_basalt\", List.of(ItemStack.of(Material.SMOOTH_BASALT, 1)));\n        expected.put(\"smooth_quartz\", List.of(ItemStack.of(Material.SMOOTH_QUARTZ, 1)));\n        expected.put(\"smooth_quartz_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"smooth_quartz_stairs\", List.of(ItemStack.of(Material.SMOOTH_QUARTZ_STAIRS, 1)));\n        expected.put(\"smooth_red_sandstone\", List.of(ItemStack.of(Material.SMOOTH_RED_SANDSTONE, 1)));\n        expected.put(\"smooth_red_sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"smooth_red_sandstone_stairs\", List.of(ItemStack.of(Material.SMOOTH_RED_SANDSTONE_STAIRS, 1)));\n        expected.put(\"smooth_sandstone\", List.of(ItemStack.of(Material.SMOOTH_SANDSTONE, 1)));\n        expected.put(\"smooth_sandstone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"smooth_sandstone_stairs\", List.of(ItemStack.of(Material.SMOOTH_SANDSTONE_STAIRS, 1)));\n        expected.put(\"smooth_stone\", List.of(ItemStack.of(Material.SMOOTH_STONE, 1)));\n        expected.put(\"smooth_stone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"sniffer_egg\", List.of(ItemStack.of(Material.SNIFFER_EGG, 1)));\n        expected.put(\"snow\", List.of());\n        expected.put(\"snow_block\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"soul_campfire\", List.of(ItemStack.of(Material.SOUL_SOIL, 1)));\n        expected.put(\"soul_fire\", List.of());\n        expected.put(\"soul_lantern\", List.of(ItemStack.of(Material.SOUL_LANTERN, 1)));\n        expected.put(\"soul_sand\", List.of(ItemStack.of(Material.SOUL_SAND, 1)));\n        expected.put(\"soul_soil\", List.of(ItemStack.of(Material.SOUL_SOIL, 1)));\n        expected.put(\"soul_torch\", List.of(ItemStack.of(Material.SOUL_TORCH, 1)));\n        expected.put(\"spawner\", List.of());\n        expected.put(\"sponge\", List.of(ItemStack.of(Material.SPONGE, 1)));\n        expected.put(\"spore_blossom\", List.of(ItemStack.of(Material.SPORE_BLOSSOM, 1)));\n        expected.put(\"spruce_button\", List.of(ItemStack.of(Material.SPRUCE_BUTTON, 1)));\n        expected.put(\"spruce_door\", List.of());\n        expected.put(\"spruce_fence\", List.of(ItemStack.of(Material.SPRUCE_FENCE, 1)));\n        expected.put(\"spruce_fence_gate\", List.of(ItemStack.of(Material.SPRUCE_FENCE_GATE, 1)));\n        expected.put(\"spruce_hanging_sign\", List.of(ItemStack.of(Material.SPRUCE_HANGING_SIGN, 1)));\n        expected.put(\"spruce_leaves\", List.of());\n        expected.put(\"spruce_log\", List.of(ItemStack.of(Material.SPRUCE_LOG, 1)));\n        expected.put(\"spruce_planks\", List.of(ItemStack.of(Material.SPRUCE_PLANKS, 1)));\n        expected.put(\"spruce_pressure_plate\", List.of(ItemStack.of(Material.SPRUCE_PRESSURE_PLATE, 1)));\n        expected.put(\"spruce_sapling\", List.of(ItemStack.of(Material.SPRUCE_SAPLING, 1)));\n        expected.put(\"spruce_sign\", List.of(ItemStack.of(Material.SPRUCE_SIGN, 1)));\n        expected.put(\"spruce_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"spruce_stairs\", List.of(ItemStack.of(Material.SPRUCE_STAIRS, 1)));\n        expected.put(\"spruce_trapdoor\", List.of(ItemStack.of(Material.SPRUCE_TRAPDOOR, 1)));\n        expected.put(\"spruce_wood\", List.of(ItemStack.of(Material.SPRUCE_WOOD, 1)));\n        expected.put(\"sticky_piston\", List.of(ItemStack.of(Material.STICKY_PISTON, 1)));\n        expected.put(\"stone\", List.of(ItemStack.of(Material.COBBLESTONE, 1)));\n        expected.put(\"stone_brick_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"stone_brick_stairs\", List.of(ItemStack.of(Material.STONE_BRICK_STAIRS, 1)));\n        expected.put(\"stone_brick_wall\", List.of(ItemStack.of(Material.STONE_BRICK_WALL, 1)));\n        expected.put(\"stone_bricks\", List.of(ItemStack.of(Material.STONE_BRICKS, 1)));\n        expected.put(\"stone_button\", List.of(ItemStack.of(Material.STONE_BUTTON, 1)));\n        expected.put(\"stone_pressure_plate\", List.of(ItemStack.of(Material.STONE_PRESSURE_PLATE, 1)));\n        expected.put(\"stone_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"stone_stairs\", List.of(ItemStack.of(Material.STONE_STAIRS, 1)));\n        expected.put(\"stonecutter\", List.of(ItemStack.of(Material.STONECUTTER, 1)));\n        expected.put(\"stripped_acacia_log\", List.of(ItemStack.of(Material.STRIPPED_ACACIA_LOG, 1)));\n        expected.put(\"stripped_acacia_wood\", List.of(ItemStack.of(Material.STRIPPED_ACACIA_WOOD, 1)));\n        expected.put(\"stripped_bamboo_block\", List.of(ItemStack.of(Material.STRIPPED_BAMBOO_BLOCK, 1)));\n        expected.put(\"stripped_birch_log\", List.of(ItemStack.of(Material.STRIPPED_BIRCH_LOG, 1)));\n        expected.put(\"stripped_birch_wood\", List.of(ItemStack.of(Material.STRIPPED_BIRCH_WOOD, 1)));\n        expected.put(\"stripped_cherry_log\", List.of(ItemStack.of(Material.STRIPPED_CHERRY_LOG, 1)));\n        expected.put(\"stripped_cherry_wood\", List.of(ItemStack.of(Material.STRIPPED_CHERRY_WOOD, 1)));\n        expected.put(\"stripped_crimson_hyphae\", List.of(ItemStack.of(Material.STRIPPED_CRIMSON_HYPHAE, 1)));\n        expected.put(\"stripped_crimson_stem\", List.of(ItemStack.of(Material.STRIPPED_CRIMSON_STEM, 1)));\n        expected.put(\"stripped_dark_oak_log\", List.of(ItemStack.of(Material.STRIPPED_DARK_OAK_LOG, 1)));\n        expected.put(\"stripped_dark_oak_wood\", List.of(ItemStack.of(Material.STRIPPED_DARK_OAK_WOOD, 1)));\n        expected.put(\"stripped_jungle_log\", List.of(ItemStack.of(Material.STRIPPED_JUNGLE_LOG, 1)));\n        expected.put(\"stripped_jungle_wood\", List.of(ItemStack.of(Material.STRIPPED_JUNGLE_WOOD, 1)));\n        expected.put(\"stripped_mangrove_log\", List.of(ItemStack.of(Material.STRIPPED_MANGROVE_LOG, 1)));\n        expected.put(\"stripped_mangrove_wood\", List.of(ItemStack.of(Material.STRIPPED_MANGROVE_WOOD, 1)));\n        expected.put(\"stripped_oak_log\", List.of(ItemStack.of(Material.STRIPPED_OAK_LOG, 1)));\n        expected.put(\"stripped_oak_wood\", List.of(ItemStack.of(Material.STRIPPED_OAK_WOOD, 1)));\n        expected.put(\"stripped_spruce_log\", List.of(ItemStack.of(Material.STRIPPED_SPRUCE_LOG, 1)));\n        expected.put(\"stripped_spruce_wood\", List.of(ItemStack.of(Material.STRIPPED_SPRUCE_WOOD, 1)));\n        expected.put(\"stripped_warped_hyphae\", List.of(ItemStack.of(Material.STRIPPED_WARPED_HYPHAE, 1)));\n        expected.put(\"stripped_warped_stem\", List.of(ItemStack.of(Material.STRIPPED_WARPED_STEM, 1)));\n        expected.put(\"sugar_cane\", List.of(ItemStack.of(Material.SUGAR_CANE, 1)));\n        expected.put(\"sunflower\", List.of());\n        expected.put(\"suspicious_gravel\", List.of());\n        expected.put(\"suspicious_sand\", List.of());\n        expected.put(\"sweet_berry_bush\", List.of());\n        expected.put(\"tall_grass\", List.of());\n        expected.put(\"tall_seagrass\", List.of());\n        expected.put(\"target\", List.of(ItemStack.of(Material.TARGET, 1)));\n        expected.put(\"terracotta\", List.of(ItemStack.of(Material.TERRACOTTA, 1)));\n        expected.put(\"tinted_glass\", List.of(ItemStack.of(Material.TINTED_GLASS, 1)));\n        expected.put(\"tnt\", List.of());\n        expected.put(\"torch\", List.of(ItemStack.of(Material.TORCH, 1)));\n        expected.put(\"torchflower\", List.of(ItemStack.of(Material.TORCHFLOWER, 1)));\n        expected.put(\"torchflower_crop\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"trapped_chest\", List.of(ItemStack.of(Material.TRAPPED_CHEST, 1)));\n        expected.put(\"trial_spawner\", List.of());\n        expected.put(\"tripwire\", List.of(ItemStack.of(Material.STRING, 1)));\n        expected.put(\"tripwire_hook\", List.of(ItemStack.of(Material.TRIPWIRE_HOOK, 1)));\n        expected.put(\"tube_coral\", List.of());\n        expected.put(\"tube_coral_block\", List.of(ItemStack.of(Material.DEAD_TUBE_CORAL_BLOCK, 1)));\n        expected.put(\"tube_coral_fan\", List.of());\n        expected.put(\"tuff\", List.of(ItemStack.of(Material.TUFF, 1)));\n        expected.put(\"tuff_brick_slab\", List.of());\n        expected.put(\"tuff_brick_stairs\", List.of());\n        expected.put(\"tuff_brick_wall\", List.of());\n        expected.put(\"tuff_bricks\", List.of());\n        expected.put(\"tuff_slab\", List.of());\n        expected.put(\"tuff_stairs\", List.of());\n        expected.put(\"tuff_wall\", List.of());\n        expected.put(\"turtle_egg\", List.of());\n        expected.put(\"twisting_vines\", List.of());\n        expected.put(\"twisting_vines_plant\", List.of());\n        expected.put(\"verdant_froglight\", List.of(ItemStack.of(Material.VERDANT_FROGLIGHT, 1)));\n        expected.put(\"vine\", List.of());\n        expected.put(\"warped_button\", List.of(ItemStack.of(Material.WARPED_BUTTON, 1)));\n        expected.put(\"warped_door\", List.of());\n        expected.put(\"warped_fence\", List.of(ItemStack.of(Material.WARPED_FENCE, 1)));\n        expected.put(\"warped_fence_gate\", List.of(ItemStack.of(Material.WARPED_FENCE_GATE, 1)));\n        expected.put(\"warped_fungus\", List.of(ItemStack.of(Material.WARPED_FUNGUS, 1)));\n        expected.put(\"warped_hanging_sign\", List.of(ItemStack.of(Material.WARPED_HANGING_SIGN, 1)));\n        expected.put(\"warped_hyphae\", List.of(ItemStack.of(Material.WARPED_HYPHAE, 1)));\n        expected.put(\"warped_nylium\", List.of(ItemStack.of(Material.NETHERRACK, 1)));\n        expected.put(\"warped_planks\", List.of(ItemStack.of(Material.WARPED_PLANKS, 1)));\n        expected.put(\"warped_pressure_plate\", List.of(ItemStack.of(Material.WARPED_PRESSURE_PLATE, 1)));\n        expected.put(\"warped_roots\", List.of(ItemStack.of(Material.WARPED_ROOTS, 1)));\n        expected.put(\"warped_sign\", List.of(ItemStack.of(Material.WARPED_SIGN, 1)));\n        expected.put(\"warped_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"warped_stairs\", List.of(ItemStack.of(Material.WARPED_STAIRS, 1)));\n        expected.put(\"warped_stem\", List.of(ItemStack.of(Material.WARPED_STEM, 1)));\n        expected.put(\"warped_trapdoor\", List.of(ItemStack.of(Material.WARPED_TRAPDOOR, 1)));\n        expected.put(\"warped_wart_block\", List.of(ItemStack.of(Material.WARPED_WART_BLOCK, 1)));\n        expected.put(\"water_cauldron\", List.of(ItemStack.of(Material.CAULDRON, 1)));\n        expected.put(\"waxed_chiseled_copper\", List.of());\n        expected.put(\"waxed_copper_block\", List.of(ItemStack.of(Material.WAXED_COPPER_BLOCK, 1)));\n        expected.put(\"waxed_copper_bulb\", List.of());\n        expected.put(\"waxed_copper_door\", List.of());\n        expected.put(\"waxed_copper_grate\", List.of());\n        expected.put(\"waxed_copper_trapdoor\", List.of());\n        expected.put(\"waxed_cut_copper\", List.of(ItemStack.of(Material.WAXED_CUT_COPPER, 1)));\n        expected.put(\"waxed_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"waxed_cut_copper_stairs\", List.of(ItemStack.of(Material.WAXED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"waxed_exposed_chiseled_copper\", List.of());\n        expected.put(\"waxed_exposed_copper\", List.of(ItemStack.of(Material.WAXED_EXPOSED_COPPER, 1)));\n        expected.put(\"waxed_exposed_copper_bulb\", List.of());\n        expected.put(\"waxed_exposed_copper_door\", List.of());\n        expected.put(\"waxed_exposed_copper_grate\", List.of());\n        expected.put(\"waxed_exposed_copper_trapdoor\", List.of());\n        expected.put(\"waxed_exposed_cut_copper\", List.of(ItemStack.of(Material.WAXED_EXPOSED_CUT_COPPER, 1)));\n        expected.put(\"waxed_exposed_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"waxed_exposed_cut_copper_stairs\", List.of(ItemStack.of(Material.WAXED_EXPOSED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"waxed_oxidized_chiseled_copper\", List.of());\n        expected.put(\"waxed_oxidized_copper\", List.of(ItemStack.of(Material.WAXED_OXIDIZED_COPPER, 1)));\n        expected.put(\"waxed_oxidized_copper_bulb\", List.of());\n        expected.put(\"waxed_oxidized_copper_door\", List.of());\n        expected.put(\"waxed_oxidized_copper_grate\", List.of());\n        expected.put(\"waxed_oxidized_copper_trapdoor\", List.of());\n        expected.put(\"waxed_oxidized_cut_copper\", List.of(ItemStack.of(Material.WAXED_OXIDIZED_CUT_COPPER, 1)));\n        expected.put(\"waxed_oxidized_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"waxed_oxidized_cut_copper_stairs\", List.of(ItemStack.of(Material.WAXED_OXIDIZED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"waxed_weathered_chiseled_copper\", List.of());\n        expected.put(\"waxed_weathered_copper\", List.of(ItemStack.of(Material.WAXED_WEATHERED_COPPER, 1)));\n        expected.put(\"waxed_weathered_copper_bulb\", List.of());\n        expected.put(\"waxed_weathered_copper_door\", List.of());\n        expected.put(\"waxed_weathered_copper_grate\", List.of());\n        expected.put(\"waxed_weathered_copper_trapdoor\", List.of());\n        expected.put(\"waxed_weathered_cut_copper\", List.of(ItemStack.of(Material.WAXED_WEATHERED_CUT_COPPER, 1)));\n        expected.put(\"waxed_weathered_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"waxed_weathered_cut_copper_stairs\", List.of(ItemStack.of(Material.WAXED_WEATHERED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"weathered_chiseled_copper\", List.of());\n        expected.put(\"weathered_copper\", List.of(ItemStack.of(Material.WEATHERED_COPPER, 1)));\n        expected.put(\"weathered_copper_bulb\", List.of());\n        expected.put(\"weathered_copper_door\", List.of());\n        expected.put(\"weathered_copper_grate\", List.of());\n        expected.put(\"weathered_copper_trapdoor\", List.of());\n        expected.put(\"weathered_cut_copper\", List.of(ItemStack.of(Material.WEATHERED_CUT_COPPER, 1)));\n        expected.put(\"weathered_cut_copper_slab\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"weathered_cut_copper_stairs\", List.of(ItemStack.of(Material.WEATHERED_CUT_COPPER_STAIRS, 1)));\n        expected.put(\"weeping_vines\", List.of());\n        expected.put(\"weeping_vines_plant\", List.of());\n        expected.put(\"wet_sponge\", List.of(ItemStack.of(Material.WET_SPONGE, 1)));\n        expected.put(\"wheat\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"white_banner\", List.of(ItemStack.of(Material.WHITE_BANNER, 1)));\n        expected.put(\"white_bed\", List.of());\n        expected.put(\"white_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"white_candle_cake\", List.of(ItemStack.of(Material.WHITE_CANDLE, 1)));\n        expected.put(\"white_carpet\", List.of(ItemStack.of(Material.WHITE_CARPET, 1)));\n        expected.put(\"white_concrete\", List.of(ItemStack.of(Material.WHITE_CONCRETE, 1)));\n        expected.put(\"white_concrete_powder\", List.of(ItemStack.of(Material.WHITE_CONCRETE_POWDER, 1)));\n        expected.put(\"white_glazed_terracotta\", List.of(ItemStack.of(Material.WHITE_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"white_shulker_box\", List.of(ItemStack.of(Material.WHITE_SHULKER_BOX, 1)));\n        expected.put(\"white_stained_glass\", List.of());\n        expected.put(\"white_stained_glass_pane\", List.of());\n        expected.put(\"white_terracotta\", List.of(ItemStack.of(Material.WHITE_TERRACOTTA, 1)));\n        expected.put(\"white_tulip\", List.of(ItemStack.of(Material.WHITE_TULIP, 1)));\n        expected.put(\"white_wool\", List.of(ItemStack.of(Material.WHITE_WOOL, 1)));\n        expected.put(\"wither_rose\", List.of(ItemStack.of(Material.WITHER_ROSE, 1)));\n        expected.put(\"wither_skeleton_skull\", List.of(ItemStack.of(Material.WITHER_SKELETON_SKULL, 1)));\n        expected.put(\"yellow_banner\", List.of(ItemStack.of(Material.YELLOW_BANNER, 1)));\n        expected.put(\"yellow_bed\", List.of());\n        expected.put(\"yellow_candle\", List.of(ItemStack.of(Material.AIR, 1)));\n        expected.put(\"yellow_candle_cake\", List.of(ItemStack.of(Material.YELLOW_CANDLE, 1)));\n        expected.put(\"yellow_carpet\", List.of(ItemStack.of(Material.YELLOW_CARPET, 1)));\n        expected.put(\"yellow_concrete\", List.of(ItemStack.of(Material.YELLOW_CONCRETE, 1)));\n        expected.put(\"yellow_concrete_powder\", List.of(ItemStack.of(Material.YELLOW_CONCRETE_POWDER, 1)));\n        expected.put(\"yellow_glazed_terracotta\", List.of(ItemStack.of(Material.YELLOW_GLAZED_TERRACOTTA, 1)));\n        expected.put(\"yellow_shulker_box\", List.of(ItemStack.of(Material.YELLOW_SHULKER_BOX, 1)));\n        expected.put(\"yellow_stained_glass\", List.of());\n        expected.put(\"yellow_stained_glass_pane\", List.of());\n        expected.put(\"yellow_terracotta\", List.of(ItemStack.of(Material.YELLOW_TERRACOTTA, 1)));\n        expected.put(\"yellow_wool\", List.of(ItemStack.of(Material.YELLOW_WOOL, 1)));\n        expected.put(\"zombie_head\", List.of(ItemStack.of(Material.ZOMBIE_HEAD, 1)));\n\n        EXPECTED_RESULTS = Map.copyOf(expected);\n    }\n\n    public static final Map<String, Map<LootContext.Trait<?>, Object>> TRAITS;\n\n    static {\n        Map<String, Map<LootContext.Trait<?>, Object>> traits = new HashMap<>();\n\n        // acacia_leaves : tool\n        traits.put(\"acacia_leaves\", Map.of(\n            LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // acacia_slab : explosion radius\n        traits.put(\"acacia_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // amethyst_cluster : explosion radius\n        traits.put(\"amethyst_cluster\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // andesite_slab : explosion radius\n        traits.put(\"andesite_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // attached_pumpkin_stem : explosion_radius\n        traits.put(\"attached_pumpkin_stem\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // azalea_leaves : tool\n        traits.put(\"azalea_leaves\", Map.of(\n            LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // bamboo_mosaic_slab : explosion_radius\n        traits.put(\"bamboo_mosaic_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // bamboo_slab : explosion_radius\n        traits.put(\"bamboo_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // barrel : block_entity\n        traits.put(\"barrel\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // beacon : block_entity\n        traits.put(\"beacon\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // beetroots : explosion_radius\n        traits.put(\"beetroots\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // birch_leaves : tool\n        traits.put(\"birch_leaves\", Map.of(\n            LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // birch_slab : explosion_radius\n        traits.put(\"birch_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // black_banner : block_entity\n        traits.put(\"black_banner\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // black_candle : explosion_radius\n        traits.put(\"black_candle\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // black_shulker_box : block_entity\n        traits.put(\"black_shulker_box\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // blackstone_slab : explosion_radius\n        traits.put(\"blackstone_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // blast_furnace : block_entity\n        traits.put(\"blast_furnace\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // blue_banner : block_entity\n        traits.put(\"blue_banner\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // blue_candle : explosion_radius\n        traits.put(\"blue_candle\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // blue_shulker_box : block_entity\n        traits.put(\"blue_shulker_box\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // bookshelf : explosion_radius\n        traits.put(\"bookshelf\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // brewing_stand : block_entity\n        traits.put(\"brewing_stand\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // brick_slab : explosion_radius\n        traits.put(\"brick_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // brown_banner : block_entity\n        traits.put(\"brown_banner\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // brown_candle : explosion_radius\n        traits.put(\"brown_candle\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // brown_mushroom : explosion_radius\n        traits.put(\"brown_mushroom_block\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // brown_shulker_box : block_entity\n        traits.put(\"brown_shulker_box\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // candle : explosion_radius\n        traits.put(\"candle\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // carrots : explosion_radius\n        traits.put(\"carrots\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cherry_leaves : tool\n        traits.put(\"cherry_leaves\", Map.of(\n            LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // cherry_slab : explosion_radius\n        traits.put(\"cherry_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // chest : block_entity\n        traits.put(\"chest\", Map.of(\n            LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // clay : explosion_radius\n        traits.put(\"clay\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // coal_ore : explosion_radius\n        traits.put(\"coal_ore\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cobbled_deepslate_slab : explosion_radius\n        traits.put(\"cobbled_deepslate_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cobblestone_slab : explosion_radius\n        traits.put(\"cobblestone_slab\", Map.of(\n            LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cocoa : explosion_radius\n        traits.put(\"cocoa\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // composter : explosion_radius\n        traits.put(\"composter\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // copper_ore : explosion_radius\n        traits.put(\"copper_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // crimson_slab : explosion_radius\n        traits.put(\"crimson_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cut_copper_slab : explosion_radius\n        traits.put(\"cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cut_red_sandstone_slab : explosion_radius\n        traits.put(\"cut_red_sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cut_sandstone_slab : explosion_radius\n        traits.put(\"cut_sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cyan_banner : block_entity\n        traits.put(\"cyan_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // cyan_candle : explosion_radius\n        traits.put(\"cyan_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // cyan_shulker_box : block_entity\n        traits.put(\"cyan_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // dark_oak_leaves : tool\n        traits.put(\"dark_oak_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // dark_oak_slab : explosion_radius\n        traits.put(\"dark_oak_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // dark_prismarine_slab : explosion_radius\n        traits.put(\"dark_prismarine_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // decorated_pot : block_entity\n        traits.put(\"decorated_pot\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // deepslate_brick_slab : explosion_radius\n        traits.put(\"deepslate_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_coal_ore : explosion_radius\n        traits.put(\"deepslate_coal_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_copper_ore : explosion_radius\n        traits.put(\"deepslate_copper_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_diamond_ore : explosion_radius\n        traits.put(\"deepslate_diamond_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_emerald_ore : explosion_radius\n        traits.put(\"deepslate_emerald_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_gold_ore : explosion_radius\n        traits.put(\"deepslate_gold_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_iron_ore : explosion_radius\n        traits.put(\"deepslate_iron_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_lapis_ore : explosion_radius\n        traits.put(\"deepslate_lapis_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // deepslate_tile_slab : explosion_radius\n        traits.put(\"deepslate_tile_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // diamond_ore : explosion_radius\n        traits.put(\"diamond_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // diorite_slab : explosion_radius\n        traits.put(\"diorite_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // dispenser : block_entity\n        traits.put(\"dispenser\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // dropper : block_entity\n        traits.put(\"dropper\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // emerald_ore : explosion_radius\n        traits.put(\"emerald_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // enchanting_table : block_entity\n        traits.put(\"enchanting_table\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // end_stone_brick_slab : explosion_radius\n        traits.put(\"end_stone_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // ender_chest : explosion_radius\n        traits.put(\"ender_chest\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // exposed_cut_copper_slab : explosion_radius\n        traits.put(\"exposed_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // flowering_azalea_leaves : tool\n        traits.put(\"flowering_azalea_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // furnace : block_entity\n        traits.put(\"furnace\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // gilded_blackstone : tool\n        traits.put(\"gilded_blackstone\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_PICKAXE, 1)\n        ));\n        // gold_ore : explosion_radius\n        traits.put(\"gold_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // granite_slab : explosion_radius\n        traits.put(\"granite_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // gravel : tool\n        traits.put(\"gravel\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.IRON_SHOVEL, 1)\n        ));\n        // gray_banner : block_entity\n        traits.put(\"gray_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // gray_candle : explosion_radius\n        traits.put(\"gray_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // gray_shulker_box : block_entity\n        traits.put(\"gray_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // green_banner : block_entity\n        traits.put(\"green_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // green_candle : explosion_radius\n        traits.put(\"green_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // green_shulker_box : block_entity\n        traits.put(\"green_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // hopper : block_entity\n        traits.put(\"hopper\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // iron_ore : explosion_radius\n        traits.put(\"iron_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // jungle_leaves : tool\n        traits.put(\"jungle_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // jungle_slab : explosion_radius\n        traits.put(\"jungle_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // lapis_ore : explosion_radius\n        traits.put(\"lapis_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // light_blue_banner : block_entity\n        traits.put(\"light_blue_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // light_blue_candle : explosion_radius\n        traits.put(\"light_blue_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // light_blue_shulker_box : block_entity\n        traits.put(\"light_blue_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // light_gray_banner : block_entity\n        traits.put(\"light_gray_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // light_gray_candle : explosion_radius\n        traits.put(\"light_gray_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // light_gray_shulker_box : block_entity\n        traits.put(\"light_gray_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // lime_banner : block_entity\n        traits.put(\"lime_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // lime_candle : explosion_radius\n        traits.put(\"lime_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // lime_shulker_box : block_entity\n        traits.put(\"lime_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // magenta_banner : block_entity\n        traits.put(\"magenta_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // magenta_candle : explosion_radius\n        traits.put(\"magenta_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // magenta_shulker_box : block_entity\n        traits.put(\"magenta_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // mangrove_leaves : tool\n        traits.put(\"mangrove_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // mangrove_slab : explosion_radius\n        traits.put(\"mangrove_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // mossy_cobblestone_slab : explosion_radius\n        traits.put(\"mossy_cobblestone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // mossy_stone_brick_slab : explosion_radius\n        traits.put(\"mossy_stone_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // mud_brick_slab : explosion_radius\n        traits.put(\"mud_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // nether_brick_slab : explosion_radius\n        traits.put(\"nether_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // nether_gold_ore : explosion_radius\n        traits.put(\"nether_gold_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // nether_quartz_ore : explosion_radius\n        traits.put(\"nether_quartz_ore\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // oak_leaves : tool\n        traits.put(\"oak_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // oak_slab : explosion_radius\n        traits.put(\"oak_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // orange_banner : block_entity\n        traits.put(\"orange_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // orange_candle : explosion_radius\n        traits.put(\"orange_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // orange_shulker_box : block_entity\n        traits.put(\"orange_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // oxidized_cut_copper_slab : explosion_radius\n        traits.put(\"oxidized_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // petrified_oak_slab : explosion_radius\n        traits.put(\"petrified_oak_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // pink_banner : block_entity\n        traits.put(\"pink_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // pink_candle : explosion_radius\n        traits.put(\"pink_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // pink_petals : explosion_radius\n        traits.put(\"pink_petals\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // pink_shulker_box : block_entity\n        traits.put(\"pink_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // player_head : block_entity\n        traits.put(\"player_head\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // polished_andesite_slab : explosion_radius\n        traits.put(\"polished_andesite_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // polished_blackstone_brick_slab : explosion_radius\n        traits.put(\"polished_blackstone_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // polished_blackstone_slab : explosion_radius\n        traits.put(\"polished_blackstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // polished_deepslate_slab : explosion_radius\n        traits.put(\"polished_deepslate_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // polished_diorite_slab : explosion_radius\n        traits.put(\"polished_diorite_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // polished_granite_slab : explosion_radius\n        traits.put(\"polished_granite_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // potatoes : explosion_radius\n        traits.put(\"potatoes\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // prismarine_brick_slab : explosion_radius\n        traits.put(\"prismarine_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // prismarine_slab : explosion_radius\n        traits.put(\"prismarine_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // purple_banner : block_entity\n        traits.put(\"purple_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // purple_candle : explosion_radius\n        traits.put(\"purple_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // purple_shulker_box : block_entity\n        traits.put(\"purple_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // purpur_slab : explosion_radius\n        traits.put(\"purpur_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // quartz_slab : explosion_radius\n        traits.put(\"quartz_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // red_banner : block_entity\n        traits.put(\"red_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // red_candle : explosion_radius\n        traits.put(\"red_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // red_nether_brick_slab : explosion_radius\n        traits.put(\"red_nether_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // red_sandstone_slab : explosion_radius\n        traits.put(\"red_sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // red_shulker_box : block_entity\n        traits.put(\"red_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // sandstone_slab : explosion_radius\n        traits.put(\"sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // sea_pickle : explosion_radius\n        traits.put(\"sea_pickle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // shulker_box : block_entity\n        traits.put(\"shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // smoker : block_entity\n        traits.put(\"smoker\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // smooth_quartz_slab : explosion_radius\n        traits.put(\"smooth_quartz_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // smooth_red_sandstone_slab : explosion_radius\n        traits.put(\"smooth_red_sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // smooth_sandstone_slab : explosion_radius\n        traits.put(\"smooth_sandstone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // smooth_stone_slab : explosion_radius\n        traits.put(\"smooth_stone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // snow_block : explosion_radius\n        traits.put(\"snow_block\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n\n        // spruce_leaves : tool\n        traits.put(\"spruce_leaves\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n\n        // spruce_slab : explosion_radius\n        traits.put(\"spruce_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // stone_brick_slab : explosion_radius\n        traits.put(\"stone_brick_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // stone_slab : explosion_radius\n        traits.put(\"stone_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // torchflower_crop : explosion_radius\n        traits.put(\"torchflower_crop\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // trapped_chest : block_entity\n        traits.put(\"trapped_chest\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // twisting_vines : tool\n        traits.put(\"twisting_vines\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // twisting_vines_plant : tool\n        traits.put(\"twisting_vines_plant\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // warped_slab : explosion_radius\n        traits.put(\"warped_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // waxed_cut_copper_slab : explosion_radius\n        traits.put(\"waxed_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // waxed_exposed_cut_copper_slab : explosion_radius\n        traits.put(\"waxed_exposed_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // waxed_oxidized_cut_copper_slab : explosion_radius\n        traits.put(\"waxed_oxidized_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // waxed_weathered_cut_copper_slab : explosion_radius\n        traits.put(\"waxed_weathered_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // weathered_cut_copper_slab : explosion_radius\n        traits.put(\"weathered_cut_copper_slab\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // weeping_vines : tool\n        traits.put(\"weeping_vines\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // weeping_vines_plant : tool\n        traits.put(\"weeping_vines_plant\", Map.of(\n                LootContext.TOOL, ItemStack.of(Material.DIAMOND_AXE, 1)\n        ));\n        // wheat : explosion_radius\n        traits.put(\"wheat\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // white_banner : block_entity\n        traits.put(\"white_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // white_candle : explosion_radius\n        traits.put(\"white_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // white_shulker_box : block_entity\n        traits.put(\"white_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // yellow_banner : block_entity\n        traits.put(\"yellow_banner\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n        // yellow_candle : explosion_radius\n        traits.put(\"yellow_candle\", Map.of(\n                LootContext.EXPLOSION_RADIUS, 0.0\n        ));\n        // yellow_shulker_box : block_entity\n        traits.put(\"yellow_shulker_box\", Map.of(\n                LootContext.BLOCK_ENTITY, CompoundBinaryTag.empty()\n        ));\n\n\n        TRAITS = Map.copyOf(traits);\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/loot/LootTableTests.java",
    "content": "package net.minestom.vanilla.datapack.loot;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.mojang.brigadier.exceptions.CommandSyntaxException;\nimport com.mojang.serialization.JsonOps;\nimport io.github.pesto.MojangDataFeature;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.TagStringIO;\nimport net.minecraft.SharedConstants;\nimport net.minecraft.core.HolderLookup;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.nbt.Tag;\nimport net.minecraft.nbt.TagParser;\nimport net.minecraft.server.Bootstrap;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.util.context.ContextKeySet;\nimport net.minecraft.util.context.ContextMap;\nimport net.minecraft.world.level.storage.loot.LootParams;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.blocks.VanillaBlockLoot;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.DatapackLoader;\nimport net.minestom.vanilla.datapack.DatapackLoadingFeature;\nimport net.minestom.vanilla.datapack.loot.context.LootContext;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport org.jetbrains.annotations.Nullable;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class LootTableTests {\n\n    private static VanillaReimplementation vri;\n    private static Datapack datapack;\n    private static FileSystem<LootTable> loot_tables;\n    private static FileSystem<String> loot_table_files;\n\n    @BeforeAll\n    public static void init() {\n        SharedConstants.tryDetectVersion();\n        Bootstrap.bootStrap();\n\n        MinecraftServer.init();\n        vri = VanillaReimplementation.hook(MinecraftServer.process());\n\n        FileSystem<ByteArray> data = vri.feature(MojangDataFeature.class).latestAssets();\n        FileSystem<String> fs = data.map(byteArray -> byteArray.toCharacterString());\n        fs = fs.folder(\"minecraft\", \"loot_tables\");\n\n        DatapackLoadingFeature feature = vri.feature(DatapackLoadingFeature.class);\n        datapack = feature.current();\n        loot_tables = datapack.namespacedData().get(\"minecraft\").loot_tables();\n        loot_table_files = fs;\n    }\n\n//    @Test\n//    public void testMinestomBlocks() {\n//        FileSystem<String> blocks = loot_table_files.folder(\"blocks\");\n//\n//        for (String file : blocks.files().stream().sorted().toList()) {\n//            String src = blocks.file(file);\n//            Table table = table(src);\n//            String filename = file.substring(0, file.length() - \".json\".length());\n//            List<ItemStack> expected = LootTableTestData.EXPECTED_RESULTS.get(filename);\n//\n//            List<Item> minestom;\n//            try {\n//                Map<LootContext.Trait<?>, Object> traits = LootTableTestData.TRAITS.getOrDefault(filename, Map.of());\n//                minestom = table.minestomLoot(traits);\n//            } catch (Exception e) {\n//                if (e instanceof IllegalStateException && e.getMessage().contains(\"LootContext does not have trait \")) {\n//                    String trait = e.getMessage().substring(\"LootContext does not have trait \".length());\n//                    System.out.println(\"Skipping test for \" + filename + \" because trait \" + trait + \" is missing\");\n//                    continue;\n//                }\n//                throw new RuntimeException(\"Failed to run LootTable: \" + file, e);\n//            }\n//\n//            if (expected == null) {\n//                String testDataValue = \"List.of(\" + minestom.stream()\n//                        .map(Item::minestom)\n//                        .map(stack -> \"ItemStack.of(Material.\" + stack.material().key().value().toUpperCase(Locale.ROOT) + \", \" + stack.amount() + \")\")\n//                        .collect(Collectors.joining(\", \")) + \")\";\n//                String testDataLine = \"expected.put(\\\"\" + filename + \"\\\", \" + testDataValue + \");\";\n//                System.out.println(testDataLine);\n//                continue;\n//            }\n//            assertItems(filename, minestom, expected);\n//        }\n//    }\n\n    private void assertItems(String label, List<Item> item, ItemStack... expected) {\n        assertItems(label, item, List.of(expected));\n    }\n\n    private void assertItems(String label, List<Item> item, List<ItemStack> expected) {\n        List<ItemStack> items = item.stream().map(Item::minestom).toList();\n        assertEquals(expected.size(), items.size(), () -> label + \" size mismatch\");\n        for (int i = 0; i < items.size(); i++) {\n            ItemStack actual = items.get(i);\n            ItemStack expectedItem = expected.get(i);\n            int finalI = i;\n            assertEquals(expectedItem.material(), actual.material(), () -> label + \" material mismatch at index \" + finalI);\n            assertEquals(expectedItem.amount(), actual.amount(), () -> label + \" amount mismatch at index \" + finalI);\n\n            // TODO: nbt comparison\n            // this is non-trivial as '{0=>CustomData[nbt=BinaryTagType[CompoundBinaryTag 10]{tags={\"tag\"=BinaryTagType[CompoundBinaryTag 10]{tags={}}}}]}' must equal '{}' (empty compound tag)\n//            assertEquals(expectedItem.nbt(), actual.nbt());\n        }\n    }\n\n    record Table(\n            net.minecraft.world.level.storage.loot.LootTable vanilla,\n            LootTable minestom\n    ) {\n        @SuppressWarnings(\"DataFlowIssue\")\n        public List<Item> vanillaLoot() {\n            // we somehow need to acquire a \"ServerLevel\" instance. Hopefully we don't need to actually run a vanilla server.\n            // check out net.minecraft.server.Main if we need to run a server\n            ServerLevel level = new ServerLevel(null, null, null, null, null, null, null, false, 0, List.of(), false, null);\n            LootParams params = new LootParams(level, new ContextMap.Builder().create(new ContextKeySet.Builder().build()), Map.of(), 0.0f);\n            var items = vanilla.getRandomItems(params);\n            return items.stream().map(LootTableTests::item).toList();\n        }\n\n        public List<Item> minestomLoot(Map<LootContext.Trait<?>, Object> traits) {\n            LootContext context = new LootContext() {\n                @Override\n                public <T> @Nullable T get(Trait<T> trait) {\n                    //noinspection unchecked\n                    return (T) traits.get(trait);\n                }\n            };\n            VanillaBlockLoot loot = new VanillaBlockLoot(vri, datapack);\n\n            var items = loot.getLoot(minestom, context);\n            return items.stream().map(LootTableTests::item).toList();\n        }\n    }\n\n    private Table table(String src) {\n        Gson gson = new Gson();\n        JsonElement json = gson.fromJson(src, JsonElement.class);\n        net.minecraft.world.level.storage.loot.LootTable vanilla = net.minecraft.world.level.storage.loot.LootTable.CODEC.parse(JsonOps.INSTANCE, json).result().orElseThrow().value();\n        LootTable minestom = DatapackLoader.adaptor(LootTable.class).apply(src);\n        return new Table(vanilla, minestom);\n    }\n\n    interface Item {\n        net.minecraft.world.item.ItemStack vanilla();\n        net.minestom.server.item.ItemStack minestom();\n    }\n\n    private static Item item(ItemStack minestom) {\n        try {\n            String snbt = TagStringIO.get().asString(minestom.toItemNBT());\n            return new SNBTItem(snbt);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static Item item(net.minecraft.world.item.ItemStack vanilla) {\n        Tag tag = vanilla.save(HolderLookup.Provider.create(Stream.empty()), new CompoundTag());\n        return new SNBTItem(tag.toString());\n    }\n}\n\nrecord SNBTItem(String item) implements LootTableTests.Item {\n    @Override\n    public net.minecraft.world.item.ItemStack vanilla() {\n        try {\n            CompoundTag tag = TagParser.parseCompoundFully(item);\n            return net.minecraft.world.item.ItemStack.parse(HolderLookup.Provider.create(Stream.empty()), tag).get();\n        } catch (CommandSyntaxException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public net.minestom.server.item.ItemStack minestom() {\n        try {\n            CompoundBinaryTag tag = TagStringIO.get().asCompound(item);\n            return ItemStack.fromItemNBT(tag);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/worldgen/DF.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.mojang.serialization.JsonOps;\nimport net.minestom.vanilla.datapack.DatapackLoader;\n\ninterface DF {\n    double compute(double x, double y, double z);\n\n    record VanillaDF(net.minecraft.world.level.levelgen.DensityFunction df) implements DF {\n        @Override\n        public double compute(double x, double y, double z) {\n            return df.compute(createFunctionContext((int) x, (int) y, (int) z));\n        }\n    }\n\n    static DF vanilla(String source) {\n        JsonElement element = new Gson().fromJson(source, JsonElement.class);\n        var result = net.minecraft.world.level.levelgen.DensityFunction.HOLDER_HELPER_CODEC.parse(JsonOps.INSTANCE, element);\n        var df = result.getOrThrow(error -> { throw new RuntimeException(error); });\n        return new VanillaDF(df);\n    }\n\n    record VriDF(DensityFunction df) implements DF {\n        @Override\n        public double compute(double x, double y, double z) {\n            return df.compute(DensityFunction.context(x, y, z));\n        }\n    }\n\n    static DF vri(String source) {\n        DensityFunction df = DatapackLoader.adaptor(DensityFunction.class).apply(source);\n        return new VriDF(df);\n    }\n\n    private static net.minecraft.world.level.levelgen.DensityFunction.FunctionContext createFunctionContext(int x, int y, int z) {\n        return new net.minecraft.world.level.levelgen.DensityFunction.FunctionContext() {\n            @Override\n            public int blockX() {\n                return x;\n            }\n\n            @Override\n            public int blockY() {\n                return y;\n            }\n\n            @Override\n            public int blockZ() {\n                return z;\n            }\n        };\n    }\n}"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/worldgen/DFVisualizer.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport com.google.common.util.concurrent.AtomicDouble;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\n\npublic class DFVisualizer {\n\n    public enum Axis {\n        X, Y, Z\n    }\n\n    static void visualize2d(String source, double scale, Axis axisToIgnore) {\n        visualize2d(DF.vanilla(source), DF.vri(source), scale, axisToIgnore);\n    }\n\n    static void visualize2d(DF vanilla, DF vri, double scale, Axis axisToSpecify) {\n        Dimension dimension = new Dimension(512, 512);\n        BufferedImage vanillaImage = generateImage(vanilla, scale, dimension, axisToSpecify, 0);\n        BufferedImage vriImage = generateImage(vri, scale, dimension, axisToSpecify, 0);\n        JLabel vanillaLabel = new JLabel(new ImageIcon(vanillaImage));\n        JLabel vriLabel = new JLabel(new ImageIcon(vriImage));\n\n        // create window, add images, and block this thread until the window is closed\n        JFrame frame = new JFrame(\"Density Function Visualizer\");\n        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\n        frame.getContentPane().setLayout(new FlowLayout());\n        frame.getContentPane().add(vanillaLabel);\n        frame.getContentPane().add(vriLabel);\n\n        // Add a slider for the axis value\n        JSlider slider = new JSlider(JSlider.HORIZONTAL, -100, 100, 0);\n        slider.setMajorTickSpacing(20);\n        slider.setPaintTicks(true);\n        slider.setPaintLabels(true);\n        // register a listener to update the image when the slider is moved\n        AtomicDouble axisValue = new AtomicDouble(0);\n        AtomicDouble target = new AtomicDouble(0);\n        slider.addChangeListener(e -> target.set(slider.getValue()));\n        Thread daemon = new Thread(() -> {\n            while (true) {\n                System.out.println(\"target: \" + target.get() + \", axisValue: \" + axisValue.get());\n                if (target.get() == axisValue.get()) {\n                    try {\n                        Thread.sleep(100);\n                    } catch (InterruptedException e) {\n                        throw new RuntimeException(e);\n                    }\n                    continue;\n                }\n                double currentTarget = target.get();\n                System.out.println(\"Updating image\");\n                BufferedImage vanillaImage1 = generateImage(vanilla, scale, dimension, axisToSpecify, currentTarget);\n                BufferedImage vriImage1 = generateImage(vri, scale, dimension, axisToSpecify, currentTarget);\n                vanillaLabel.setIcon(new ImageIcon(vanillaImage1));\n                vriLabel.setIcon(new ImageIcon(vriImage1));\n                frame.pack();\n                frame.repaint();\n                axisValue.set(currentTarget);\n            }\n        });\n        daemon.setDaemon(true);\n        daemon.start();\n        frame.getContentPane().add(slider);\n        frame.pack();\n        frame.setVisible(true);\n        try {\n            Thread.sleep(Long.MAX_VALUE);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static BufferedImage generateImage(DF df, double scale, Dimension imageDimensions, Axis axisToSpecify, double axisValue) {\n        BufferedImage image = new BufferedImage(imageDimensions.width, imageDimensions.height, BufferedImage.TYPE_INT_RGB);\n        Graphics2D graphics = image.createGraphics();\n        graphics.setColor(Color.BLACK);\n        graphics.fillRect(0, 0, imageDimensions.width, imageDimensions.height);\n\n        double xStep = scale / imageDimensions.width;\n        double zStep = scale / imageDimensions.height;\n\n        for (int x = 0; x < imageDimensions.width; x++) {\n            for (int z = 0; z < imageDimensions.height; z++) {\n                double xCoord = ((double) x - (double) imageDimensions.width / 2.0) * xStep;\n                double zCoord = ((double) z - (double) imageDimensions.height / 2.0) * zStep;\n                double yCoord = switch (axisToSpecify) {\n                    case X -> df.compute(axisValue, xCoord, zCoord);\n                    case Y -> df.compute(xCoord, axisValue, zCoord);\n                    case Z -> df.compute(xCoord, zCoord, axisValue);\n                };\n                double alpha = Math.min(1.0, Math.max(0.0, yCoord));\n                graphics.setColor(new Color(1.0f, 1.0f, 1.0f, (float) alpha));\n                graphics.fillRect(x, z, 1, 1);\n            }\n        }\n\n        image.flush();\n        return image;\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/worldgen/DensityFunctionTests.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.minecraft.SharedConstants;\nimport net.minecraft.server.Bootstrap;\nimport net.minestom.server.coordinate.Vec;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Random;\nimport java.util.function.BiConsumer;\nimport java.util.function.Supplier;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class DensityFunctionTests {\n\n    private static final double DELTA = 0.0001;\n\n    @BeforeAll\n    public static void prepare() {\n        SharedConstants.tryDetectVersion();\n        Bootstrap.bootStrap();\n    }\n\n    // End islands are used in many other tests. So test them first.\n    private final String END_ISLANDS = \"{ \\\"type\\\": \\\"minecraft:end_islands\\\" }\";\n    @Test\n    public void testEndIslands() {\n        assertExact(END_ISLANDS);\n    }\n\n    @Test\n    public void testConstant() {\n        assertExact(\"1.0\");\n        assertExact(\"0.0\");\n        assertExact(\"-1.0\");\n        assertExact(\"0.5\");\n        assertExact(\"1\");\n        assertExact(\"0\");\n    }\n\n    @Test\n    public void testClamp() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:clamp\",\n                  \"input\": %s,\n                  \"min\": -0.5,\n                  \"max\": 0.5\n                }\n                \"\"\", END_ISLANDS));\n        assertExact(String.format(\"\"\"\n                                {\n                  \"type\": \"minecraft:clamp\",\n                  \"input\": %s,\n                  \"min\": -0.23,\n                  \"max\": 1.0\n                }\n                \"\"\", END_ISLANDS));\n        assertExact(String.format(\"\"\"\n                                {\n                  \"type\": \"minecraft:clamp\",\n                  \"input\": %s,\n                  \"min\": -1.0,\n                  \"max\": 0.23\n                }\n                \"\"\", END_ISLANDS));\n        assertExact(String.format(\"\"\"\n                                {\n                  \"type\": \"minecraft:clamp\",\n                  \"input\": %s,\n                  \"min\": -0.23,\n                  \"max\": 0.23\n                }\n                \"\"\", END_ISLANDS));\n        assertExact(String.format(\"\"\"\n                                {\n                  \"type\": \"minecraft:clamp\",\n                  \"input\": %s,\n                  \"min\": 0.23,\n                  \"max\": 0.23\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testAbs() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:abs\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testSquare() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:square\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    private final String END_ISLANDS_CUBED = String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:cube\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS);\n    @Test\n    public void testCube() {\n        assertExact(END_ISLANDS_CUBED);\n    }\n\n    @Test\n    public void testHalfNegative() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:half_negative\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testQuarterNegative() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:quarter_negative\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testSqueeze() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:squeeze\",\n                  \"argument\": %s\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testAdd() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:add\",\n                  \"argument1\": %s,\n                  \"argument2\": %s\n                }\n                \"\"\", END_ISLANDS, END_ISLANDS_CUBED));\n    }\n\n    @Test\n    public void testMul() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:mul\",\n                  \"argument1\": %s,\n                  \"argument2\": %s\n                }\n                \"\"\", END_ISLANDS, END_ISLANDS_CUBED));\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:mul\",\n                  \"argument1\": %s,\n                  \"argument2\": 2.0\n                }\n                \"\"\", END_ISLANDS));\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:mul\",\n                  \"argument1\": %s,\n                  \"argument2\": 128\n                }\n                \"\"\", END_ISLANDS));\n    }\n\n    @Test\n    public void testMin() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:min\",\n                  \"argument1\": %s,\n                  \"argument2\": %s\n                }\n                \"\"\", END_ISLANDS, END_ISLANDS_CUBED));\n    }\n\n    @Test\n    public void testMax() {\n        assertExact(String.format(\"\"\"\n                {\n                  \"type\": \"minecraft:max\",\n                  \"argument1\": %s,\n                  \"argument2\": %s\n                }\n                \"\"\", END_ISLANDS, END_ISLANDS_CUBED));\n    }\n\n    // Noise is the big boi, so test it thoroughly.\n    @Test\n    public void testNoise() {\n        assertExact(\"\"\"\n                {\n                  \"type\": \"minecraft:noise\",\n                  \"noise\": {\n                    \"firstOctave\": -3,\n                    \"amplitudes\": [\n                      1\n                    ]\n                  },\n                  \"xz_scale\": 1.0,\n                  \"y_scale\": 1.0\n                }\n                \"\"\");\n    }\n\n    private void testPositions(BiConsumer<Vec, Integer> consumer) {\n        double dist = 0.0001;\n\n        Random random = new Random(0);\n        int i = 0;\n        while (dist < 100000.0) {\n            dist *= 1.1;\n\n            double x = random.nextDouble(-dist, dist);\n            double y = random.nextDouble(-dist, dist);\n            double z = random.nextDouble(-dist, dist);\n            consumer.accept(new Vec(x, y, z), i++);\n        }\n    }\n\n    private void assertExact(String source) {\n        assertExact(DF.vanilla(source), DF.vri(source), DELTA);\n    }\n\n    private void assertExact(DF vanilla, DF vri, double delta) {\n        testPositions((pos, i) -> {\n            var result = compare(vanilla, vri, pos.blockX(), pos.blockY(), pos.blockZ());\n            int finalI = i;\n            result.assertEqual(delta, () -> \"Failed at \" + pos + \" (index \" + finalI + \")\");\n        });\n    }\n\n    private record Result(double vanilla, double vri) {\n        public void assertEqual(double delta, Supplier<String> message) {\n            assertEquals(vanilla, vri, delta, message);\n        }\n    }\n\n    private Result compare(DF vanilla, DF vri, int x, int y, int z) {\n        var vanillaRes = vanilla.compute(x, y, z);\n        var vriRes = vri.compute(x, y, z);\n        return new Result(vanillaRes, vriRes);\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/worldgen/NoiseTests.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.minecraft.world.level.levelgen.LegacyRandomSource;\nimport net.minestom.vanilla.datapack.worldgen.noise.SimplexNoise;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Random;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class NoiseTests {\n\n    @Test\n    public void testSimplex() {\n        testSimplex(0);\n        testSimplex(123);\n        testSimplex(123456789);\n        testSimplex(-123456789);\n    }\n\n    private void testSimplex(long seed) {\n        Noise vanilla = simplexVanilla(seed);\n        Noise vri = simplexVri(seed);\n        Random random = new Random(seed);\n\n        // 3d\n        for (int i = 0; i < 1000; i++) {\n            double bound = Math.sqrt(i + 1);\n            double x = random.nextDouble(-bound, bound);\n            double y = random.nextDouble(-bound, bound);\n            double z = random.nextDouble(-bound, bound);\n            double vanillaRes = vanilla.sample(x, y, z);\n            double vriRes = vri.sample(x, y, z);\n            assertEquals(vanillaRes, vriRes, 0.000001, \"x=\" + x + \", y=\" + y + \", z=\" + z + \" failed (i=\" + i + \")\");\n        }\n\n        // 2d\n        for (int i = 0; i < 1000; i++) {\n            double bound = Math.sqrt(i + 1);\n            double x = random.nextDouble(-bound, bound);\n            double y = random.nextDouble(-bound, bound);\n            double vanillaRes = vanilla.sample2d(x, y);\n            double vriRes = vri.sample2d(x, y);\n            assertEquals(vanillaRes, vriRes, 0.000001, \"x=\" + x + \", y=\" + y + \" failed (i=\" + i + \")\");\n        }\n    }\n\n\n    private interface Noise {\n        double sample(double x, double y, double z);\n        double sample2d(double x, double y);\n    }\n\n\n    private static Noise simplexVanilla(long seed) {\n        var noise = new net.minecraft.world.level.levelgen.synth.SimplexNoise(new LegacyRandomSource(seed));\n        return new Noise() {\n            @Override\n            public double sample(double x, double y, double z) {\n                return noise.getValue(x, y, z);\n            }\n\n            @Override\n            public double sample2d(double x, double y) {\n                return noise.getValue(x, y);\n            }\n        };\n    }\n\n    private static Noise simplexVri(long seed) {\n        var noise = new SimplexNoise(WorldgenRandom.legacy(seed));\n        return new Noise() {\n            @Override\n            public double sample(double x, double y, double z) {\n                return noise.sample(x, y, z);\n            }\n\n            @Override\n            public double sample2d(double x, double y) {\n                return noise.sample2D(x, y);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "datapack-tests/src/test/java/net/minestom/vanilla/datapack/worldgen/RandomTests.java",
    "content": "package net.minestom.vanilla.datapack.worldgen;\n\nimport net.minecraft.world.level.levelgen.LegacyRandomSource;\nimport net.minecraft.world.level.levelgen.XoroshiroRandomSource;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class RandomTests {\n\n    @Test\n    public void testLegacyRandom() {\n        testLegacyRandom(0);\n        testLegacyRandom(534);\n        testLegacyRandom(-49273);\n        testLegacyRandom(123456789);\n    }\n\n    private void testLegacyRandom(long seed) {\n        var vri = WorldgenRandom.legacy(seed);\n        var vanilla = new LegacyRandomSource(seed);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextInt(), vri.nextInt(), \"Iteration \" + i);\n            assertEquals(vanilla.nextInt(i + 8), vri.nextInt(i + 8), \"Iteration \" + i);\n        }\n\n        vri.consumeInt(19283);\n        vanilla.consumeCount(19283);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextLong(), vri.nextLong(), \"Iteration \" + i);\n        }\n\n        vri.consumeInt(19283);\n        vanilla.consumeCount(19283);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextDouble(), vri.nextDouble(), \"Iteration \" + i);\n        }\n    }\n\n    @Test\n    public void testXoroshiroRandom() {\n        var vri = WorldgenRandom.xoroshiro(0);\n        var vanilla = new XoroshiroRandomSource(0);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextInt(), vri.nextInt(), \"Iteration \" + i);\n            assertEquals(vanilla.nextInt(i + 8), vri.nextInt(i + 8), \"Iteration \" + i);\n        }\n\n        vri.consumeLong(19283);\n        vanilla.consumeCount(19283);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextLong(), vri.nextLong(), \"Iteration \" + i);\n        }\n\n        vri.consumeInt(19283);\n        vanilla.consumeCount(19283);\n\n        for (int i = 0; i < 100; i++) {\n            assertEquals(vanilla.nextDouble(), vri.nextDouble(), \"Iteration \" + i);\n        }\n    }\n}\n"
  },
  {
    "path": "entities/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":entity-meta\"))\n}"
  },
  {
    "path": "entities/src/main/java/net/minestom/vanilla/entities/FallingBlockEntity.java",
    "content": "package net.minestom.vanilla.entities;\n\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.metadata.other.FallingBlockMeta;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.VanillaRegistry;\nimport net.minestom.vanilla.entitymeta.EntityTags;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.Random;\n\npublic class FallingBlockEntity extends Entity {\n    private static final Random rng = new Random();\n    private final @NotNull Block toPlace;\n\n    public FallingBlockEntity(@NotNull Block toPlace, @NotNull Pos initialPosition) {\n        super(EntityType.FALLING_BLOCK);\n        this.toPlace = toPlace;\n\n        // setGravity(0.025f, getGravityAcceleration());\n        setBoundingBox(0.98f, 0.98f, 0.98f);\n\n        FallingBlockMeta meta = (FallingBlockMeta) this.getEntityMeta();\n        meta.setBlock(toPlace);\n        meta.setSpawnPosition(initialPosition);\n    }\n\n    public FallingBlockEntity(@NotNull VanillaRegistry.EntityContext context) {\n        this(Objects.requireNonNullElse(context.getTag(EntityTags.FallingBlock.BLOCK), Block.AIR), context.position());\n    }\n\n    @Override\n    public void update(long time) {\n        // TODO: Cleanup this method structure\n\n        // TODO: This isOnGround method seems to snap the entity to the ground earlier than expected\n        if (!isOnGround()) {\n            return;\n        }\n\n        Block block = instance.getBlock(position);\n\n        if (block.registry().isSolid()) {\n            // TODO: Better way to get block's loot\n            Material loot = block.registry().material();\n            if (loot != null) {\n                ItemEntity itemEntity = new ItemEntity(ItemStack.of(loot));\n                itemEntity.setInstance(instance, position);\n            }\n            remove();\n            return;\n        }\n\n        instance.setBlock(position, toPlace);\n        remove();\n    }\n}\n"
  },
  {
    "path": "entities/src/main/java/net/minestom/vanilla/entities/MinestomEntitiesFeature.java",
    "content": "package net.minestom.vanilla.entities;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\npublic class MinestomEntitiesFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        context.registry().register(EntityType.FALLING_BLOCK, FallingBlockEntity::new);\n        context.registry().register(EntityType.TNT, PrimedTNTEntity::new);\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:entities\");\n    }\n}\n"
  },
  {
    "path": "entities/src/main/java/net/minestom/vanilla/entities/PrimedTNTEntity.java",
    "content": "package net.minestom.vanilla.entities;\n\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.metadata.other.PrimedTntMeta;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaRegistry;\nimport net.minestom.vanilla.entitymeta.EntityTags;\nimport net.minestom.vanilla.instance.VanillaExplosion;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\npublic class PrimedTNTEntity extends Entity {\n\n    private int fuseTime;\n\n    public PrimedTNTEntity(@NotNull VanillaRegistry.EntityContext context) {\n        this(Objects.requireNonNullElse(context.getTag(EntityTags.PrimedTnt.FUSE_TIME), 80));\n    }\n\n    public PrimedTNTEntity(int fuseTime) {\n        super(EntityType.TNT);\n        setAerodynamics(getAerodynamics().withVerticalAirResistance(0.98f));\n        setBoundingBox(0.98f, 0.98f, 0.98f);\n        this.fuseTime = fuseTime;\n\n        PrimedTntMeta meta = (PrimedTntMeta) this.getEntityMeta();\n        meta.setFuseTime(fuseTime);\n    }\n\n    private void explode() {\n        Instance instance = this.instance;\n        remove();\n\n        Block block = instance.getBlock(this.getPosition());\n\n        VanillaExplosion explosion = VanillaExplosion.builder(getPosition(), 4.0f)\n                .destroyBlocks(!block.isLiquid())\n                .build();\n\n        explosion.trigger(instance);\n    }\n\n    @Override\n    public void update(long time) {\n        super.update(time);\n        if (fuseTime-- <= 0) {\n            explode();\n        }\n    }\n\n    public int getFuseTime() {\n        return fuseTime;\n    }\n\n    public void setFuseTime(int fuseTime) {\n        this.fuseTime = fuseTime;\n    }\n}\n"
  },
  {
    "path": "entity-meta/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "entity-meta/src/main/java/net/minestom/vanilla/entitymeta/EntityTags.java",
    "content": "package net.minestom.vanilla.entitymeta;\n\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.tag.Tag;\nimport org.jetbrains.annotations.NotNull;\n\npublic interface EntityTags {\n\n    interface FallingBlock {\n        @NotNull Tag<Block> BLOCK = Tag.String(\"vri:entity-meta.falling_block.block\")\n                .map(Block::fromKey, block -> block.key().toString());\n    }\n\n    interface PrimedTnt {\n        @NotNull Tag<Integer> FUSE_TIME = Tag.Integer(\"vri:entity-meta.primed_tnt.fuse_time\");\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":block-update-system\"))\n}"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/EmptyFluid.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.utils.Direction;\n\npublic class EmptyFluid extends Fluid {\n\n    public EmptyFluid() {\n        super(Block.AIR, Material.BUCKET);\n    }\n\n    @Override\n    protected boolean canBeReplacedWith(Instance instance, Point point, Fluid other, Direction direction) {\n        return true;\n    }\n\n    @Override\n    public int getNextTickDelay(Instance instance, Point point, Block block) {\n        return -1;\n    }\n\n    @Override\n    protected boolean isEmpty() {\n        return true;\n    }\n\n    @Override\n    protected double getBlastResistance() {\n        return 0;\n    }\n\n    @Override\n    public double getHeight(Block block, Instance instance, Point point) {\n        return 0;\n    }\n\n    @Override\n    public double getHeight(Block block) {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/FlowableFluid.java",
    "content": "package io.github.togar2.fluids;\n\nimport it.unimi.dsi.fastutil.shorts.Short2BooleanMap;\nimport it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.gamedata.tags.Tag;\nimport net.minestom.server.gamedata.tags.TagManager;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.utils.Direction;\n\nimport java.util.EnumMap;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic abstract class FlowableFluid extends Fluid {\n\n    public FlowableFluid(Block defaultBlock, Material bucket) {\n        super(defaultBlock, bucket);\n    }\n\n    @Override\n    public void onTick(Instance instance, Point point, Block block) {\n        if (!isSource(block)) {\n            Block updated = getUpdatedState(instance, point, block);\n            if (MinestomFluids.get(updated).isEmpty()) {\n                block = updated;\n                instance.setBlock(point, Block.AIR);\n            } else if (updated != block) {\n                block = updated;\n                instance.setBlock(point, updated);\n            }\n        }\n        tryFlow(instance, point, block);\n    }\n\n    @Override\n    public int getNextTickDelay(Instance instance, Point point, Block block) {\n        return getTickRate(instance);\n    }\n\n    protected void tryFlow(Instance instance, Point point, Block block) {\n        Fluid fluid = MinestomFluids.get(block);\n        if (fluid.isEmpty()) return;\n\n        Point down = point.add(0, -1, 0);\n        Block downBlock = instance.getBlock(down);\n        Block updatedDownFluid = getUpdatedState(instance, down, downBlock);\n        if (canFlow(instance, point, block, Direction.DOWN, down, downBlock, updatedDownFluid)) {\n            flow(instance, down, downBlock, Direction.DOWN, updatedDownFluid);\n            if (getAdjacentSourceCount(instance, point) >= 3) {\n                flowSides(instance, point, block);\n            }\n        } else if (isSource(block) || !canFlowDown(instance, updatedDownFluid, point, block, down, downBlock)) {\n            flowSides(instance, point, block);\n        }\n    }\n\n    /**\n     * Flows to the sides whenever possible, or to a hole if found\n     */\n    private void flowSides(Instance instance, Point point, Block block) {\n        int newLevel = getLevel(block) - getLevelDecreasePerBlock(instance);\n        if (isFalling(block)) newLevel = 7;\n        if (newLevel <= 0) return;\n\n        Map<Direction, Block> map = getSpread(instance, point, block);\n        for (Map.Entry<Direction, Block> entry : map.entrySet()) {\n            Direction direction = entry.getKey();\n            Block newBlock = entry.getValue();\n            Point offset = point.add(direction.normalX(), direction.normalY(), direction.normalZ());\n            Block currentBlock = instance.getBlock(offset);\n            if (!canFlow(instance, point, block, direction, offset, currentBlock, newBlock)) continue;\n            flow(instance, offset, currentBlock, direction, newBlock);\n        }\n    }\n\n    /**\n     * Gets the updated state of a source block by taking into account its surrounding blocks.\n     */\n    protected Block getUpdatedState(Instance instance, Point point, Block block) {\n        int highestLevel = 0;\n        int stillCount = 0;\n        for (Direction direction : Direction.HORIZONTAL) {\n            Point directionPos = point.add(direction.normalX(), direction.normalY(), direction.normalZ());\n            Block directionBlock = instance.getBlock(directionPos);\n            Fluid directionFluid = MinestomFluids.get(directionBlock);\n            if (directionFluid != this || !receivesFlow(direction, instance, point, block, directionPos, directionBlock))\n                continue;\n\n            if (isSource(directionBlock)) {\n                ++stillCount;\n            }\n            highestLevel = Math.max(highestLevel, getLevel(directionBlock));\n        }\n\n        if (isInfinite() && stillCount >= 2) {\n            // If there's 2 or more still fluid blocks around\n            // and below is still or a solid block, make this block still\n            Block downBlock = instance.getBlock(point.add(0, -1, 0));\n            if (downBlock.isSolid() || isMatchingAndStill(downBlock)) {\n                return getSource(false);\n            }\n        }\n\n        Point above = point.add(0, 1, 0);\n        Block aboveBlock = instance.getBlock(above);\n        Fluid aboveFluid = MinestomFluids.get(aboveBlock);\n        if (!aboveFluid.isEmpty() && aboveFluid == this\n                && receivesFlow(Direction.UP, instance, point, block, above, aboveBlock)) {\n            return getFlowing(8, true);\n        }\n\n        int newLevel = highestLevel - getLevelDecreasePerBlock(instance);\n        if (newLevel <= 0) return Block.AIR;\n        return getFlowing(newLevel, false);\n    }\n\n    private boolean receivesFlow(Direction face, Instance instance, Point point,\n                                 Block block, Point fromPoint, Block fromBlock) {\n        // Vanilla seems to check if the adjacent block shapes cover the same square, but this seems to work as well\n        // (Might not work with some special blocks)\n        // If there is anything wrong it is most likely this method :D\n\n        if (block.isLiquid()) {\n            if (face == Direction.UP) {\n                if (fromBlock.isLiquid()) return true;\n                return block.isSolid() || block.isAir();\n                //return isSource(block) || getLevel(block) == 8;\n            } else if (face == Direction.DOWN) {\n                if (fromBlock.isLiquid()) return true;\n                return fromBlock.isSolid() || fromBlock.isAir();\n                //return isSource(fromBlock) || getLevel(fromBlock) == 8;\n            } else {\n                return true;\n            }\n        } else {\n            if (face == Direction.UP) {\n                return block.isSolid() || block.isAir();\n            } else if (face == Direction.DOWN) {\n                return block.isSolid() || block.isAir();\n            } else {\n                return block.isSolid() || block.isAir();\n            }\n        }\n    }\n\n    /**\n     * Creates a unique id based on the relation between point and point2\n     */\n    private static short getID(Point point, Point point2) {\n        int i = (int) (point2.x() - point.x());\n        int j = (int) (point2.z() - point.z());\n        return (short) ((i + 128 & 0xFF) << 8 | j + 128 & 0xFF);\n    }\n\n    /**\n     * Returns a map with the directions the water can flow in and the block the water will become in that direction.\n     * If a hole is found within {@code getHoleRadius()} blocks, the water will only flow in that direction.\n     * A weight is used to determine which hole is the closest.\n     */\n    protected Map<Direction, Block> getSpread(Instance instance, Point point, Block block) {\n        int weight = 1000;\n        EnumMap<Direction, Block> map = new EnumMap<>(Direction.class);\n        Short2BooleanOpenHashMap holeMap = new Short2BooleanOpenHashMap();\n\n        for (Direction direction : Direction.HORIZONTAL) {\n            Point directionPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ());\n            Block directionBlock = instance.getBlock(directionPoint);\n            short id = FlowableFluid.getID(point, directionPoint);\n\n            Block updatedBlock = getUpdatedState(instance, directionPoint, directionBlock);\n            if (!canFlowThrough(instance, updatedBlock, point, block, direction, directionPoint, directionBlock))\n                continue;\n\n            boolean down = holeMap.computeIfAbsent(id, s -> {\n                Point downPoint = directionPoint.add(0, -1, 0);\n                return canFlowDown(\n                        instance, getFlowing(getLevel(updatedBlock), false),\n                        directionPoint, directionBlock, downPoint, instance.getBlock(downPoint)\n                );\n            });\n\n            int newWeight = down ? 0 : getWeight(instance, directionPoint, 1,\n                    direction.opposite(), directionBlock, point, holeMap);\n            if (newWeight < weight) map.clear();\n\n            if (newWeight <= weight) {\n                map.put(direction, updatedBlock);\n                weight = newWeight;\n            }\n        }\n        return map;\n    }\n\n    protected int getWeight(Instance instance, Point point, int initialWeight, Direction skipCheck,\n                            Block block, Point originalPoint, Short2BooleanMap short2BooleanMap) {\n        int weight = 1000;\n        for (Direction direction : Direction.HORIZONTAL) {\n            if (direction == skipCheck) continue;\n            Point directionPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ());\n            Block directionBlock = instance.getBlock(directionPoint);\n            short id = FlowableFluid.getID(originalPoint, directionPoint);\n\n            if (!canFlowThrough(instance, getFlowing(getLevel(block), false), point, block,\n                    direction, directionPoint, directionBlock)) continue;\n\n            boolean down = short2BooleanMap.computeIfAbsent(id, s -> {\n                Point downPoint = directionPoint.add(0, -1, 0);\n                Block downBlock = instance.getBlock(downPoint);\n                return canFlowDown(\n                        instance, getFlowing(getLevel(block), false),\n                        directionPoint, downBlock, downPoint, downBlock\n                );\n            });\n            if (down) return initialWeight;\n\n            if (initialWeight < getHoleRadius(instance)) {\n                int newWeight = getWeight(instance, directionPoint, initialWeight + 1,\n                        direction.opposite(), directionBlock, originalPoint, short2BooleanMap);\n                if (newWeight < weight) weight = newWeight;\n            }\n        }\n        return weight;\n    }\n\n    private int getAdjacentSourceCount(Instance instance, Point point) {\n        int i = 0;\n        for (Direction direction : Direction.HORIZONTAL) {\n            Point currentPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ());\n            Block block = instance.getBlock(currentPoint);\n            if (!isMatchingAndStill(block)) continue;\n            ++i;\n        }\n        return i;\n    }\n\n    /**\n     * Returns whether the fluid can flow through a specific block\n     */\n    private boolean canFill(Instance instance, Point point, Block block, Block flowing) {\n        //TODO check waterloggable\n        TagManager tags = MinecraftServer.getTagManager();\n        if (block.compare(Block.LADDER)\n                || block.compare(Block.SUGAR_CANE)\n                || block.compare(Block.BUBBLE_COLUMN)\n                || block.compare(Block.NETHER_PORTAL)\n                || block.compare(Block.END_PORTAL)\n                || block.compare(Block.END_GATEWAY)\n                || block.compare(Block.KELP)\n                || block.compare(Block.KELP_PLANT)\n                || block.compare(Block.SEAGRASS)\n                || block.compare(Block.TALL_SEAGRASS)\n                || block.compare(Block.SEA_PICKLE)\n                || Objects.requireNonNull(tags.getTag(Tag.BasicType.BLOCKS, \"minecraft:signs\")).contains(block.key())\n                || block.name().contains(\"door\")\n                || block.name().contains(\"coral\")) {\n            return false;\n        }\n        return !block.isSolid();\n    }\n\n    private boolean canFlowDown(Instance instance, Block flowing, Point point,\n                                Block block, Point fromPoint, Block fromBlock) {\n        if (!this.receivesFlow(Direction.DOWN, instance, point, block, fromPoint, fromBlock)) return false;\n        if (MinestomFluids.get(fromBlock) == this) return true;\n        return this.canFill(instance, fromPoint, fromBlock, flowing);\n    }\n\n    private boolean canFlowThrough(Instance instance, Block flowing, Point point, Block block,\n                                   Direction face, Point fromPoint, Block fromBlock) {\n        return !isMatchingAndStill(fromBlock)\n                && receivesFlow(face, instance, point, block, fromPoint, fromBlock)\n                && canFill(instance, fromPoint, fromBlock, flowing);\n    }\n\n    protected boolean canFlow(Instance instance, Point fluidPoint, Block flowingBlock,\n                              Direction flowDirection, Point flowTo, Block flowToBlock, Block newFlowing) {\n        return MinestomFluids.get(flowToBlock).canBeReplacedWith(instance, flowTo, MinestomFluids.get(newFlowing), flowDirection)\n                && receivesFlow(flowDirection, instance, fluidPoint, flowingBlock, flowTo, flowToBlock)\n                && canFill(instance, flowTo, flowToBlock, newFlowing);\n    }\n\n    /**\n     * Sets the position to the new block, executing {@code onBreakingBlock()} before breaking any non-air block.\n     */\n    protected void flow(Instance instance, Point point, Block block, Direction direction, Block newBlock) {\n        //TODO waterloggable check\n        boolean cancel = false;\n        if (!block.isAir()) {\n            if (!onBreakingBlock(instance, point, block))\n                cancel = true;\n        }\n\n        if (!cancel) instance.setBlock(point, newBlock);\n    }\n\n    private boolean isMatchingAndStill(Block block) {\n        return MinestomFluids.get(block) == this && isSource(block);\n    }\n\n    public Block getFlowing(int level, boolean falling) {\n        return defaultBlock.withProperty(\"level\", String.valueOf(falling ? 8 : level));\n    }\n\n    public Block getSource(boolean falling) {\n        return falling ? defaultBlock.withProperty(\"level\", \"8\") : defaultBlock;\n    }\n\n    protected abstract boolean isInfinite();\n\n    protected abstract int getLevelDecreasePerBlock(Instance instance);\n\n    protected abstract int getHoleRadius(Instance instance);\n\n    /**\n     * Returns whether the block can be broken\n     */\n    protected abstract boolean onBreakingBlock(Instance instance, Point point, Block block);\n\n    public abstract int getTickRate(Instance instance);\n\n    private static boolean isFluidAboveEqual(Block block, Instance instance, Point point) {\n        return MinestomFluids.get(block) == MinestomFluids.get(instance.getBlock(point.add(0, 1, 0)));\n    }\n\n    @Override\n    public double getHeight(Block block, Instance instance, Point point) {\n        return isFluidAboveEqual(block, instance, point) ? 1 : getHeight(block);\n    }\n\n    @Override\n    public double getHeight(Block block) {\n        return getLevel(block) / 9.0;\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/Fluid.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.utils.Direction;\n\npublic abstract class Fluid {\n    protected final Block defaultBlock;\n    private final ItemStack bucket;\n\n    public Fluid(Block block, Material bucket) {\n        this.defaultBlock = block;\n        this.bucket = ItemStack.of(bucket);\n    }\n\n    public Block getDefaultBlock() {\n        return defaultBlock;\n    }\n\n    public ItemStack getBucket() {\n        return bucket;\n    }\n\n    protected abstract boolean canBeReplacedWith(Instance instance, Point point,\n                                                 Fluid other, Direction direction);\n\n    public abstract int getNextTickDelay(Instance instance, Point point, Block block);\n\n    public void onTick(Instance instance, Point point, Block block) {\n    }\n\n    protected boolean isEmpty() {\n        return false;\n    }\n\n    protected abstract double getBlastResistance();\n\n    public abstract double getHeight(Block block, Instance instance, Point point);\n\n    public abstract double getHeight(Block block);\n\n    public static boolean isSource(Block block) {\n        String levelStr = block.getProperty(\"level\");\n        return levelStr == null || Integer.parseInt(levelStr) == 0;\n    }\n\n    public static int getLevel(Block block) {\n        String levelStr = block.getProperty(\"level\");\n        if (levelStr == null) return 8;\n        int level = Integer.parseInt(levelStr);\n        if (level == 0) return 8; // Source block\n        return level;\n    }\n\n    public static boolean isFalling(Block block) {\n        String levelStr = block.getProperty(\"level\");\n        if (levelStr == null) return false;\n        return Integer.parseInt(levelStr) >= 8;\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/FluidPlacementRule.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.rule.BlockPlacementRule;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\npublic class FluidPlacementRule extends BlockPlacementRule {\n\n    public FluidPlacementRule(@NotNull Block block) {\n        super(block);\n    }\n\n    @Override\n    public @Nullable Block blockPlace(@NotNull PlacementState placementState) {\n        if (placementState.instance() instanceof Instance instance) {\n            MinestomFluids.scheduleTick(instance, placementState.placePosition(), placementState.block());\n        }\n        return placementState.block();\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/FluidSimulationFeature.java",
    "content": "package io.github.togar2.fluids;\n\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.BlockUpdateFeature;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\n\npublic class FluidSimulationFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        // TODO: Use the block-update-system\n        MinestomFluids.init(context.vri().process());\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"io.github.togar2:fluids\");\n    }\n\n    @NotNull\n    public Set<Class<? extends VanillaReimplementation.Feature>> dependencies() {\n        return Set.of(BlockUpdateFeature.class);\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/MinestomFluids.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.instance.InstanceTickEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\n\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class MinestomFluids {\n    public static final Fluid WATER = new WaterFluid();\n    public static final Fluid EMPTY = new EmptyFluid();\n\n    private static final Map<Instance, Map<Long, Set<Point>>> UPDATES = new ConcurrentHashMap<>();\n\n    public static Fluid get(Block block) {\n        if (block.compare(Block.WATER)) {\n            return WATER;\n        } else if (block.compare(Block.LAVA)) {\n            return EMPTY;\n        } else {\n            return EMPTY;\n        }\n    }\n\n    public static void tick(InstanceTickEvent event) {\n        Set<Point> currentUpdate = UPDATES.computeIfAbsent(event.getInstance(), i -> new ConcurrentHashMap<>())\n                .get(event.getInstance().getWorldAge());\n        if (currentUpdate == null) return;\n        for (Point point : currentUpdate) {\n            tick(event.getInstance(), point);\n        }\n        UPDATES.get(event.getInstance()).remove(event.getInstance().getWorldAge());\n    }\n\n    public static void tick(Instance instance, Point point) {\n        get(instance.getBlock(point)).onTick(instance, point, instance.getBlock(point));\n    }\n\n    public static void scheduleTick(Instance instance, Point point, Block block) {\n        int tickDelay = MinestomFluids.get(block).getNextTickDelay(instance, point, block);\n        if (tickDelay == -1) return;\n\n        //TODO figure out a way to remove instance from map if unregistered?\n        long newAge = instance.getWorldAge() + tickDelay;\n        UPDATES.get(instance).computeIfAbsent(newAge, l -> new HashSet<>()).add(point);\n    }\n\n    public static void init(ServerProcess process) {\n        process.block().registerBlockPlacementRule(new FluidPlacementRule(Block.WATER));\n        process.eventHandler().addChild(events());\n    }\n\n    public static EventNode<Event> events() {\n        EventNode<Event> node = EventNode.all(\"fluid-events\");\n        node.addListener(InstanceTickEvent.class, MinestomFluids::tick);\n        return node;\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/WaterBlockBreakEvent.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.event.trait.BlockEvent;\nimport net.minestom.server.event.trait.CancellableEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport org.jetbrains.annotations.NotNull;\n\npublic class WaterBlockBreakEvent implements InstanceEvent, BlockEvent, CancellableEvent {\n    private final Instance instance;\n    private final BlockVec blockPosition;\n    private final Block block;\n\n    private boolean cancelled;\n\n    public WaterBlockBreakEvent(@NotNull Instance instance, @NotNull BlockVec blockPosition, @NotNull Block block) {\n        this.instance = instance;\n        this.blockPosition = blockPosition;\n        this.block = block;\n    }\n\n    @Override\n    public @NotNull Instance getInstance() {\n        return instance;\n    }\n\n    public @NotNull BlockVec getBlockPosition() {\n        return blockPosition;\n    }\n\n    @Override\n    public @NotNull Block getBlock() {\n        return block;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancel) {\n        this.cancelled = cancel;\n    }\n}\n"
  },
  {
    "path": "fluid-simulation/src/main/java/io/github/togar2/fluids/WaterFluid.java",
    "content": "package io.github.togar2.fluids;\n\nimport net.minestom.server.coordinate.BlockVec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.utils.Direction;\n\npublic class WaterFluid extends FlowableFluid {\n\n    public WaterFluid() {\n        super(Block.WATER, Material.WATER_BUCKET);\n    }\n\n    @Override\n    protected boolean isInfinite() {\n        return true;\n    }\n\n    @Override\n    protected boolean onBreakingBlock(Instance instance, Point point, Block block) {\n        WaterBlockBreakEvent event = new WaterBlockBreakEvent(instance, new BlockVec(point), block);\n        return !event.isCancelled();\n    }\n\n    @Override\n    protected int getHoleRadius(Instance instance) {\n        return 4;\n    }\n\n    @Override\n    public int getLevelDecreasePerBlock(Instance instance) {\n        return 1;\n    }\n\n    @Override\n    public int getTickRate(Instance instance) {\n        return 5;\n    }\n\n    @Override\n    protected boolean canBeReplacedWith(Instance instance, Point point, Fluid other, Direction direction) {\n        return direction == Direction.DOWN && this == other;\n    }\n\n    @Override\n    protected double getBlastResistance() {\n        return 100;\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.7-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "minestom_version=2025.07.04-1.21.5\nrayfast_version=684e854a48\njnoise_version=4.0.0\nannotations_version=23.0.0\nwindow_version=1.1\nslf4j_version=2.0.16"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd \"${APP_HOME:-./}\" > /dev/null && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "instance-meta/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "instance-meta/src/main/java/net/minestom/vanilla/instancemeta/InstanceMetaFeature.java",
    "content": "package net.minestom.vanilla.instancemeta;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.event.instance.InstanceTickEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.instancemeta.tickets.TicketManager;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\npublic class InstanceMetaFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        new Logic().hook(context.vri());\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:instancemeta\");\n    }\n\n    private static class Logic {\n\n        private final @NotNull Map<Instance, TicketManager> instance2TicketManager =\n                Collections.synchronizedMap(new WeakHashMap<>());\n\n        private Logic() {\n        }\n\n        private void hook(@NotNull VanillaReimplementation vri) {\n            vri.process().eventHandler().addListener(InstanceTickEvent.class, event -> tickInstance(event.getInstance()));\n        }\n\n        // Process all future tickets\n        private void tickInstance(@NotNull Instance instance) {\n            TicketManager ticketManager = instance2TicketManager.computeIfAbsent(instance, ignored -> new TicketManager());\n\n            List<TicketManager.Ticket> waitingForceLoads = instance.getTag(TicketManager.WAITING_TICKETS_TAG);\n            if (waitingForceLoads == null) {\n                return;\n            }\n\n            for (TicketManager.Ticket waitingForceLoad : waitingForceLoads) {\n                ticketManager.addTicket(waitingForceLoad);\n            }\n\n            instance.setTag(TicketManager.WAITING_TICKETS_TAG, List.of());\n        }\n    }\n}\n"
  },
  {
    "path": "instance-meta/src/main/java/net/minestom/vanilla/instancemeta/tickets/TicketManager.java",
    "content": "package net.minestom.vanilla.instancemeta.tickets;\n\nimport it.unimi.dsi.fastutil.longs.Long2ObjectMap;\nimport it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;\nimport it.unimi.dsi.fastutil.longs.Long2ShortMap;\nimport it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.Object2ShortMap;\nimport it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;\nimport it.unimi.dsi.fastutil.shorts.Short2IntMap;\nimport it.unimi.dsi.fastutil.shorts.Short2IntOpenHashMap;\nimport net.minestom.server.coordinate.CoordConversion;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.server.tag.TagReadable;\nimport net.minestom.server.tag.TagSerializer;\nimport net.minestom.server.tag.TagWritable;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/*\nTickets are used to choose when & how to load chunks, when and how to tick entities, and when and how to tick\nBlockHandlers.\n\nTickets range:\nInaccessible: 0 and below -> No game aspects are active, but world generation still occurs.\nBorder: 1 -> Only some game aspects are active (Redstone and command blocks do not work).\nTicking: 2 -> All game aspects are active except that entities are not processed (do not move) and chunk ticks aren't either.\nEntity Ticking: 3 and above -> All game aspects are active.\n\nVanilla Ticket Value -> vri Ticket Value:\nvriTicketValue = 34 - vanillaTicketValue\n */\n\n/**\n * A utility class used to manage instance's tickets\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic class TicketManager {\n\n    public static final Tag<List<Ticket>> WAITING_TICKETS_TAG =\n            Tag.Structure(\"vri:instancemeta:waiting_tickets\", TicketManager.Ticket.SERIALIZER)\n                    .list();\n\n    public static final Tag<List<Ticket>> REMOVING_TICKETS_TAG =\n            Tag.Structure(\"vri:instancemeta:removing_tickets\", TicketManager.Ticket.SERIALIZER)\n                    .list();\n\n    // Vanilla ticket values\n    public static final short PLAYER_TICKET = 34 - 31;\n    public static final short FORCED_TICKET = 34 - 31;\n    public static final short START_TICKET = 34 - 22;\n    public static final short PORTAL_TICKET = 34 - 30;\n    public static final short DRAGON_TICKET = 34 - 24;\n    public static final short POST_TELEPORT_TICKET = 34 - 33;\n    public static final short SPREAD_PLAYERS_TICKET = 34 - 33;\n    public static final short END_PORTAL_TICKET = 34 - 33;\n    public static final short TEMPORARY_TICKET = 34 - 33;\n\n    /**\n     * The first Long is the target chunk\n     * The second Long is the source chunk\n     * The resulting Short is the value for this chunk\n     */\n    private final Object2ShortMap<Source2Target> externalTicketValues = new Object2ShortOpenHashMap<>();\n    private final Long2ObjectMap<Short2IntMap> internalTicketValues = new Long2ObjectOpenHashMap<>();\n    private final Long2ShortMap currentTicketValue = new Long2ShortOpenHashMap();\n\n\n    public interface Ticket {\n        short value();\n\n        long chunk();\n\n        static @NotNull Ticket from(short value, long chunk) {\n            return new TicketImpl(value, chunk);\n        }\n\n        Tag<Short> VALUE_TAG = Tag.Short(\"vri:instancemeta:ticket_value\");\n        Tag<Long> CHUNK_TAG = Tag.Long(\"vri:instancemeta:ticket_chunk\");\n\n        TagSerializer<Ticket> SERIALIZER = new TagSerializer<>() {\n            @Override\n            public @Nullable Ticket read(@NotNull TagReadable reader) {\n                Short value = reader.getTag(VALUE_TAG);\n                Long chunk = reader.getTag(CHUNK_TAG);\n                if (value == null || chunk == null) {\n                    return null;\n                }\n                return new TicketImpl(value, chunk);\n            }\n\n            @Override\n            public void write(@NotNull TagWritable writer, @NotNull Ticket value) {\n                writer.setTag(VALUE_TAG, value.value());\n                writer.setTag(CHUNK_TAG, value.chunk());\n            }\n        };\n    }\n\n    private record TicketImpl(short value, long chunk) implements Ticket {\n    }\n\n    private record Source2Target(long source, long target) {\n    }\n\n    public TicketManager() {\n    }\n\n    // Ticket methods\n\n    /**\n     * Adds a ticket and updates surrounding chunks.\n     *\n     * @param ticket the ticket to add\n     */\n    public void addTicket(@NotNull Ticket ticket) {\n        addTicket(ticket.value(), ticket.chunk());\n    }\n\n    /**\n     * Adds a ticket to the specified chunk and updates surrounding chunks.\n     *\n     * @param chunk the chunk index of the chunk to add the ticket to\n     * @param value the value of the ticket\n     */\n    public void addTicket(short value, long chunk) {\n\n        // Add value to internal\n        Short2IntMap internalValues = internalTicketValues.get(chunk);\n        internalValues.putIfAbsent(value, 0);\n        internalValues.put(value, internalValues.get(value) + 1);\n\n        externalTicketValues.put(new Source2Target(chunk, chunk), value);\n        recalculateChunkValue(chunk);\n\n        // Add values to surrounding external\n        short currentSourceValue = value;\n        int originX = CoordConversion.chunkIndexGetX(chunk);\n        int originZ = CoordConversion.chunkIndexGetZ(chunk);\n\n        while (currentSourceValue > 1) {\n            // Find starting positions + starting external value\n            int halfWidth = currentSourceValue - 1;\n            short externalValue = (short) (value - currentSourceValue + 1);\n\n            // Left starting position\n            int leftX = originX - halfWidth;\n            int leftZ = originZ + halfWidth;\n\n            // Top starting position\n            int topX = originX - halfWidth;\n            int topZ = originZ - halfWidth;\n\n            // Right starting position\n            int rightX = originX + halfWidth;\n            int rightZ = originZ - halfWidth;\n\n            // Down starting position\n            int downX = originX + halfWidth;\n            int downZ = originZ + halfWidth;\n\n            // Do all squares\n            for (int offset = 0; offset < ((halfWidth * 2) + 1); offset++) {\n\n                { // Left side\n                    long chunkIndex = CoordConversion.chunkIndex(leftX, leftZ - offset);\n                    externalTicketValues.put(new Source2Target(chunkIndex, chunk), externalValue);\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Top side\n                    long chunkIndex = CoordConversion.chunkIndex(topX + offset, topZ);\n                    externalTicketValues.put(new Source2Target(chunkIndex, chunk), externalValue);\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Right side\n                    long chunkIndex = CoordConversion.chunkIndex(rightX, rightZ + offset);\n                    externalTicketValues.put(new Source2Target(chunkIndex, chunk), externalValue);\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Downside\n                    long chunkIndex = CoordConversion.chunkIndex(downX - offset, downZ);\n                    externalTicketValues.put(new Source2Target(chunkIndex, chunk), externalValue);\n                    recalculateChunkValue(chunkIndex);\n                }\n            }\n\n            currentSourceValue--;\n        }\n    }\n\n    /**\n     * Removes a ticket from this chunk and updates the surrounding chunks\n     *\n     * @param chunk the chunk index of the chunk to remove the ticket from\n     * @param value the value of the ticket being removed\n     */\n    public void removeTicket(long chunk, short value) {\n\n        // Remove value from internal\n        Short2IntMap internalValues = internalTicketValues.get(chunk);\n\n        if (internalValues.containsKey(value)) {\n            int current = internalValues.get(value);\n\n            if (current == 1) {\n                internalValues.remove(value);\n            } else {\n                internalValues.put(value, current - 1);\n            }\n            return;\n        }\n\n        // Remove value from external access into this chunk\n        externalTicketValues.removeShort(new Source2Target(chunk, chunk));\n        recalculateChunkValue(chunk);\n\n        // Remove values from surrounding external\n        short highestInternalValue = 0;\n\n        for (short internalValue : internalValues.keySet()) {\n            if (internalValue > highestInternalValue) {\n                highestInternalValue = internalValue;\n            }\n        }\n\n        if (highestInternalValue > 0) {\n            externalTicketValues.put(new Source2Target(chunk, chunk), highestInternalValue);\n        }\n\n        short previousSourceValue = value;\n        int originX = CoordConversion.chunkIndexGetX(chunk);\n        int originZ = CoordConversion.chunkIndexGetZ(chunk);\n\n        while (previousSourceValue > 1) {\n            // Find starting positions + starting external value\n            int halfWidth = previousSourceValue - 1;\n\n            // Left starting position\n            int leftX = originX - halfWidth;\n            int leftZ = originZ + halfWidth;\n\n            // Top starting position\n            int topX = originX - halfWidth;\n            int topZ = originZ - halfWidth;\n\n            // Right starting position\n            int rightX = originX + halfWidth;\n            int rightZ = originZ - halfWidth;\n\n            // Down starting position\n            int downX = originX + halfWidth;\n            int downZ = originZ + halfWidth;\n\n            // Do all squares\n            for (int offset = 0; offset < ((halfWidth * 2) + 1); offset++) {\n\n                { // Left side\n                    long chunkIndex = CoordConversion.chunkIndex(leftX, leftZ - offset);\n                    if (highestInternalValue <= 0) {\n                        externalTicketValues.removeShort(new Source2Target(chunkIndex, chunk));\n                    } else {\n                        externalTicketValues.put(new Source2Target(chunkIndex, chunk), highestInternalValue);\n                    }\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Top side\n                    long chunkIndex = CoordConversion.chunkIndex(topX + offset, topZ);\n                    if (highestInternalValue <= 0) {\n                        externalTicketValues.removeShort(new Source2Target(chunkIndex, chunk));\n                    } else {\n                        externalTicketValues.put(new Source2Target(chunkIndex, chunk), highestInternalValue);\n                    }\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Right side\n                    long chunkIndex = CoordConversion.chunkIndex(rightX, rightZ + offset);\n                    if (highestInternalValue <= 0) {\n                        externalTicketValues.removeShort(new Source2Target(chunkIndex, chunk));\n                    } else {\n                        externalTicketValues.put(new Source2Target(chunkIndex, chunk), highestInternalValue);\n                    }\n                    recalculateChunkValue(chunkIndex);\n                }\n\n                { // Downside\n                    long chunkIndex = CoordConversion.chunkIndex(downX - offset, downZ);\n                    if (highestInternalValue <= 0) {\n                        externalTicketValues.removeShort(new Source2Target(chunkIndex, chunk));\n                    } else {\n                        externalTicketValues.put(new Source2Target(chunkIndex, chunk), highestInternalValue);\n                    }\n                    recalculateChunkValue(chunkIndex);\n                }\n            }\n\n            previousSourceValue--;\n            highestInternalValue--;\n        }\n    }\n\n    /**\n     * Gets the ticket value of the specified chunk.\n     *\n     * @param chunkIndex the chunk index of the chunk to retrieve the ticket value from\n     * @return the ticket value\n     */\n    public short getTicketValue(long chunkIndex) {\n        return currentTicketValue.get(chunkIndex);\n    }\n\n    /**\n     * Gets information on the tickets for this specified chunk\n     *\n     * @param chunkIndex the chunk index of the chunk to retrieve the ticket info from\n     * @return the ticket value\n     */\n    public String getChunkInfo(long chunkIndex) {\n\n        return \"Current Value: \" + currentTicketValue.get(chunkIndex) + \"\\n\"\n                + \"Internal Tickets: \" + internalTicketValues.get(chunkIndex) + \"\\n\"\n                + \"External Tickets: \" + \" (\" + externalTicketValues.object2ShortEntrySet()\n                .stream()\n                .filter(entry -> entry.getKey().target() == chunkIndex)\n                .map(String::valueOf)\n                .collect(Collectors.joining(\", \")) + \" )\";\n    }\n\n    private void handleInstanceChunkLoad(long chunkIndex) {\n        synchronized (this) {\n            prepareChunk(chunkIndex);\n        }\n    }\n\n    private void prepareChunk(long chunk) {\n        currentTicketValue.computeIfAbsent(chunk, ignored -> (short) 0);\n        internalTicketValues.computeIfAbsent(chunk, k -> new Short2IntOpenHashMap());\n    }\n\n    private static class MutableShort {\n        private short value;\n    }\n\n    private void recalculateChunkValue(long chunkIndex) {\n        prepareChunk(chunkIndex);\n\n        MutableShort highest = new MutableShort();\n        highest.value = 0;\n\n        externalTicketValues.forEach((source2Target, value) -> {\n            if (source2Target.target() == chunkIndex) {\n                if (value > highest.value) {\n                    highest.value = value;\n                }\n            }\n        });\n\n        // Set new value\n        this.currentTicketValue.put(chunkIndex, highest.value);\n    }\n}"
  },
  {
    "path": "instance-meta/src/main/java/net/minestom/vanilla/instancemeta/tickets/TicketUtils.java",
    "content": "package net.minestom.vanilla.instancemeta.tickets;\n\nimport net.minestom.server.instance.Instance;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Stream;\n\npublic class TicketUtils {\n\n    public static @NotNull List<TicketManager.Ticket> waitingTickets(@NotNull Instance instance) {\n        return instance.getTag(TicketManager.WAITING_TICKETS_TAG);\n    }\n\n    public static void waitingTickets(@NotNull Instance instance, @NotNull Collection<TicketManager.Ticket> ticketsToAdd) {\n        List<TicketManager.Ticket> newWaitingTickets = Stream.concat(\n                waitingTickets(instance).stream(),\n                ticketsToAdd.stream()\n        ).toList();\n        instance.setTag(TicketManager.WAITING_TICKETS_TAG, newWaitingTickets);\n    }\n\n    public static @NotNull List<TicketManager.Ticket> removingTickets(Instance instance) {\n        return instance.getTag(TicketManager.REMOVING_TICKETS_TAG);\n    }\n\n    public static void removingTickets(Instance instance, @NotNull Collection<TicketManager.Ticket> from) {\n        List<TicketManager.Ticket> newRemovingTickets = Stream.concat(\n                removingTickets(instance).stream(),\n                from.stream()\n        ).toList();\n        instance.setTag(TicketManager.REMOVING_TICKETS_TAG, newRemovingTickets);\n    }\n}\n"
  },
  {
    "path": "item-placeables/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "item-placeables/src/main/java/net/minestom/vanilla/itemplaceables/ItemPlaceablesFeature.java",
    "content": "package net.minestom.vanilla.itemplaceables;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.event.player.PlayerUseItemOnBlockEvent;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class ItemPlaceablesFeature implements VanillaReimplementation.Feature {\n    @Override\n    public void hook(@NotNull HookContext context) {\n\n\n        // TODO: Flesh this out and make it configurable\n        Map<Material, Block> itemPlaceables = new ConcurrentHashMap<>();\n\n        itemPlaceables.put(Material.WATER_BUCKET, Block.WATER);\n        itemPlaceables.put(Material.LAVA_BUCKET, Block.LAVA);\n\n        context.vri().process().eventHandler().addListener(PlayerUseItemOnBlockEvent.class, event -> {\n            Point position = event.getPosition();\n            var face = event.getBlockFace();\n            ItemStack item = event.getItemStack();\n\n            Block block = itemPlaceables.get(item.material());\n            if (block == null) return;\n            position = position.relative(face);\n            event.getInstance().setBlock(position, block);\n        });\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:item-placeables\");\n    }\n}\n"
  },
  {
    "path": "items/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "items/src/main/java/net/minestom/vanilla/items/FlintAndSteelHandler.java",
    "content": "package net.minestom.vanilla.items;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.PlayerHand;\nimport net.minestom.server.event.player.PlayerUseItemOnBlockEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.BlockHandler;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.utils.Direction;\nimport net.minestom.vanilla.inventory.InventoryManipulation;\n\npublic class FlintAndSteelHandler implements VanillaItemHandler {\n    public FlintAndSteelHandler() {\n    }\n\n    @Override\n    public boolean onUseOnBlock(PlayerUseItemOnBlockEvent event) {\n        // TODO: check if flammable\n        Point pos = event.getPosition();\n        Player player = event.getPlayer();\n        Instance instance = player.getInstance();\n        ItemStack itemStack = event.getItemStack();\n        PlayerHand hand = event.getHand();\n        Direction blockDir = event.getBlockFace().toDirection();\n\n        // Find block in direction\n        Point firePosition = pos.add(\n                blockDir.normalX(),\n                blockDir.normalY(),\n                blockDir.normalZ()\n        );\n\n        Block atFirePosition = instance.getBlock(firePosition);\n\n        if (atFirePosition.isAir()) {\n            InventoryManipulation.damageItemIfNotCreative(player, hand, 1);\n            // Block block, Instance instance, Point blockPosition, Player player, Player.Hand hand,\n            // BlockFace blockFace, float cursorX, float cursorY, float cursorZ\n            instance.placeBlock(new BlockHandler.PlayerPlacement(\n                    Block.FIRE,\n                    instance,\n                    firePosition,\n                    player,\n                    hand,\n                    event.getBlockFace(),\n                    0, 0, 0 // TODO: cursor position via raycast\n            ));\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "items/src/main/java/net/minestom/vanilla/items/ItemManager.java",
    "content": "package net.minestom.vanilla.items;\n\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.player.PlayerUseItemEvent;\nimport net.minestom.server.event.player.PlayerUseItemOnBlockEvent;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class ItemManager {\n\n    public static @NotNull ItemManager accumulate(@NotNull Consumer<Accumulator> accumulator) {\n        Map<Material, VanillaItemHandler> itemHandlersByMaterial = new HashMap<>();\n        accumulator.accept(itemHandlersByMaterial::put);\n        return new ItemManager(itemHandlersByMaterial);\n    }\n\n    public interface Accumulator {\n        void accumulate(@NotNull Material material, @NotNull VanillaItemHandler itemHandler);\n    }\n\n    private final Map<Material, VanillaItemHandler> itemHandlersByMaterial;\n\n    private ItemManager(Map<Material, VanillaItemHandler> itemHandlersByMaterial) {\n        this.itemHandlersByMaterial = Map.copyOf(itemHandlersByMaterial);\n    }\n\n    private void handlePlayerUseItemEvent(PlayerUseItemEvent event) {\n        ItemStack itemStack = event.getItemStack();\n\n        VanillaItemHandler itemHandler = itemHandlersByMaterial.get(itemStack.material());\n\n        if (itemHandler == null) {\n            return;\n        }\n\n        itemHandler.onUseInAir(event);\n    }\n\n    private void handlePlayerUseItemOnBlockEvent(PlayerUseItemOnBlockEvent event) {\n        ItemStack itemStack = event.getItemStack();\n\n        VanillaItemHandler itemHandler = itemHandlersByMaterial.get(itemStack.material());\n\n        if (itemHandler == null) {\n            return;\n        }\n\n        itemHandler.onUseOnBlock(event);\n    }\n\n    public void registerEvents(EventNode<Event> itemEventNode) {\n        itemEventNode.addListener(\n                EventListener.of(PlayerUseItemEvent.class, this::handlePlayerUseItemEvent)\n        );\n\n        itemEventNode.addListener(\n                EventListener.of(PlayerUseItemOnBlockEvent.class, this::handlePlayerUseItemOnBlockEvent)\n        );\n    }\n}\n"
  },
  {
    "path": "items/src/main/java/net/minestom/vanilla/items/ItemsFeature.java",
    "content": "package net.minestom.vanilla.items;\n\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport org.jetbrains.annotations.NotNull;\n\npublic class ItemsFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        ItemManager manager = ItemManager.accumulate(accumulator -> {\n            for (VanillaItems item : VanillaItems.values()) {\n                accumulator.accumulate(item.getMaterial(), item.getItemHandlerSupplier().get());\n            }\n        });\n        manager.registerEvents(context.vri().process().eventHandler());\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:items\");\n    }\n}\n"
  },
  {
    "path": "items/src/main/java/net/minestom/vanilla/items/VanillaItemHandler.java",
    "content": "package net.minestom.vanilla.items;\n\nimport net.minestom.server.event.player.PlayerUseItemEvent;\nimport net.minestom.server.event.player.PlayerUseItemOnBlockEvent;\n\npublic interface VanillaItemHandler {\n\n    /**\n     * Called when the player right clicks with this item in the air\n     *\n     * @param event the event object\n     */\n    default void onUseInAir(PlayerUseItemEvent event) {\n    }\n\n    /**\n     * Called when the player right clicks with this item on a block\n     *\n     * @param event the event object\n     * @return true if it prevents normal item use (placing blocks for instance)\n     */\n    default boolean onUseOnBlock(PlayerUseItemOnBlockEvent event) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "items/src/main/java/net/minestom/vanilla/items/VanillaItems.java",
    "content": "package net.minestom.vanilla.items;\n\nimport net.minestom.server.item.Material;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Supplier;\n\n/**\n * All items with special behaviour available in the vanilla reimplementation\n */\npublic enum VanillaItems {\n\n    FLINT_AND_STEEL(Material.FLINT_AND_STEEL, FlintAndSteelHandler::new);\n\n    private final Material material;\n    private final Supplier<VanillaItemHandler> itemCreator;\n\n    VanillaItems(@NotNull Material material, Supplier<VanillaItemHandler> itemCreator) {\n        this.itemCreator = itemCreator;\n        this.material = material;\n    }\n\n    public @NotNull Material getMaterial() {\n        return material;\n    }\n\n    public @NotNull Supplier<VanillaItemHandler> getItemHandlerSupplier() {\n        return itemCreator;\n    }\n}\n"
  },
  {
    "path": "jitpack.yml",
    "content": "jdk:\n  - openjdk21\n"
  },
  {
    "path": "loot-table/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":datapack\"))\n}"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/BlockExperience.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.random.RandomGenerator;\n\n/**\n * Utilities for calculating how much experience ought to be dropped when a block is broken.\n */\npublic class BlockExperience {\n\n    /**\n     * Gets the amount of experience the given block should drop when mined.\n     */\n    public static int getExperience(@NotNull Block minedBlock, @NotNull ItemStack tool, @NotNull RandomGenerator random) {\n        // TODO: Will have to take into account EffectComponent#BLOCK_EXPERIENCE on Enchantments if it is ever used.\n\n        if (tool.get(DataComponents.ENCHANTMENTS, EnchantmentList.EMPTY).has(Enchantment.SILK_TOUCH)) return 0;\n\n        return switch (EXPERIENCE.get(minedBlock.id())) {\n            case Amount.Constant(int points) -> points;\n            case Amount.Uniform(int min, int max) -> random.nextInt(min, max + 1);\n            case null -> 0;\n        };\n    }\n\n    /**\n     * An amount of experience - either a constant or uniform.\n     */\n    public sealed interface Amount {\n\n        record Constant(int amount) implements Amount {}\n        record Uniform(int min, int max) implements Amount {}\n\n    }\n\n    /**\n     * Unfortunately, this cannot be datagenned. This is not a consistent property in the code, and cannot be reliably\n     * checked. Naive checks will detect most of these blocks (but not all), and will label some other blocks as\n     * constant 0. Implementing this correctly requires code introspection abilities that are not reasonable to\n     * implement, especially given the small number (16) of blocks that actually drop experience.\n     */\n    private static final @NotNull Int2ObjectMap<Amount> EXPERIENCE = new Int2ObjectArrayMap<>(Map.ofEntries(\n            // Ores\n            entry(Block.COAL_ORE, new Amount.Uniform(0, 2)),\n            entry(Block.DEEPSLATE_COAL_ORE, new Amount.Uniform(0, 2)),\n            entry(Block.LAPIS_ORE, new Amount.Uniform(2, 5)),\n            entry(Block.DEEPSLATE_LAPIS_ORE, new Amount.Uniform(2, 5)),\n            entry(Block.REDSTONE_ORE, new Amount.Uniform(1, 5)),\n            entry(Block.DEEPSLATE_REDSTONE_ORE, new Amount.Uniform(1, 5)),\n            entry(Block.DIAMOND_ORE, new Amount.Uniform(3, 7)),\n            entry(Block.DEEPSLATE_DIAMOND_ORE, new Amount.Uniform(3, 7)),\n            entry(Block.EMERALD_ORE, new Amount.Uniform(3, 7)),\n            entry(Block.DEEPSLATE_EMERALD_ORE, new Amount.Uniform(3, 7)),\n            entry(Block.NETHER_GOLD_ORE, new Amount.Uniform(0, 1)),\n            entry(Block.NETHER_QUARTZ_ORE, new Amount.Uniform(2, 5)),\n\n            // Sculk\n            entry(Block.SCULK, new Amount.Constant(1)),\n            entry(Block.SCULK_SHRIEKER, new Amount.Constant(5)),\n            entry(Block.SCULK_SENSOR, new Amount.Constant(5)),\n            entry(Block.SCULK_CATALYST, new Amount.Constant(5))\n    ));\n\n    private static Map.Entry<Integer, Amount> entry(Block block, Amount amount) {\n        return Map.entry(block.id(), amount);\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootContext.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Random;\n\n/**\n * Stores a dynamic amount of information that may be relevant during the generation of loot.\n */\npublic sealed interface LootContext permits LootContextImpl {\n\n    @NotNull LootContext.Key<Random> RANDOM = LootContext.key(\"minecraft:random\");\n    @NotNull LootContext.Key<Float> EXPLOSION_RADIUS = LootContext.key(\"minecraft:explosion_radius\");\n    @NotNull LootContext.Key<Player> LAST_DAMAGE_PLAYER = LootContext.key(\"minecraft:last_damage_player\");\n    @NotNull LootContext.Key<Instance> WORLD = LootContext.key(\"minecraft:world\");\n    @NotNull LootContext.Key<ItemStack> TOOL = LootContext.key(\"minecraft:tool\");\n    @NotNull LootContext.Key<Boolean> ENCHANTMENT_ACTIVE = LootContext.key(\"minecraft:enchantment_active\");\n    @NotNull LootContext.Key<Block> BLOCK_STATE = LootContext.key(\"minecraft:block_state\");\n    @NotNull LootContext.Key<DamageType> DAMAGE_SOURCE = LootContext.key(\"minecraft:damage_source\");\n    @NotNull LootContext.Key<Point> ORIGIN = LootContext.key(\"minecraft:origin\");\n    @NotNull LootContext.Key<Entity> DIRECT_ATTACKING_ENTITY = LootContext.key(\"minecraft:direct_attacking_entity\");\n    @NotNull LootContext.Key<Entity> ATTACKING_ENTITY = LootContext.key(\"minecraft:attacking_entity\");\n    @NotNull LootContext.Key<Entity> THIS_ENTITY = LootContext.key(\"minecraft:this_entity\");\n    @NotNull LootContext.Key<Double> LUCK = LootContext.key(\"minecraft:luck\");\n    @NotNull LootContext.Key<Integer> ENCHANTMENT_LEVEL = LootContext.key(\"minecraft:enchantment_level\");\n\n    /**\n     * Creates a loot context from the provided map of key -> object.\n     * @param data the values of the context\n     * @return the new context instance\n     */\n    static @NotNull LootContext from(@NotNull Map<Key<?>, Object> data) {\n        return LootContextImpl.from(data);\n    }\n\n    /**\n     * Creates a key from the provided key.\n     */\n    static <T> LootContext.@NotNull Key<T> key(@NotNull String key) {\n        return new LootContext.Key<>(key);\n    }\n\n    /**\n     * Represents a key that stores information in a loot context.\n     * @param id the string id of the key\n     * @param <T> the type parameter of the key\n     */\n    @SuppressWarnings(\"unused\")\n    record Key<T>(@NotNull String id) {}\n\n    /**\n     * Returns whether or not this context has the provided key.\n     * @param key the key to search for\n     * @return true if this context has the key, false if not\n     */\n    boolean has(@NotNull Key<?> key);\n\n    /**\n     * Gets the object associated with the provided key, returning null if not.\n     * @param key the key to search for\n     * @return the optional value\n     * @param <T> the type of object desired\n     */\n    <T> @Nullable T get(@NotNull Key<T> key);\n\n    /**\n     * Gets the object associated with the provided key, returning the default value if not.\n     * @param key the key to search for\n     * @param defaultValue the default value to use\n     * @return the optional value\n     * @param <T> the type of object desired\n     */\n    <T> @NotNull T get(@NotNull Key<T> key, @NotNull T defaultValue);\n\n    /**\n     * Gets the object associated with the provided key, throwing an exception if not.\n     * @param key the key to search for\n     * @return the object associated with the provided key\n     * @param <T> the type of object desired\n     */\n    <T> @NotNull T require(@NotNull Key<T> key);\n\n}\n\nrecord LootContextImpl(@NotNull Map<String, Object> data) implements LootContext {\n\n    LootContextImpl {\n        data = Map.copyOf(data);\n    }\n\n    static @NotNull LootContext from(@NotNull Map<Key<?>, Object> data) {\n        Map<String, Object> mapped = new HashMap<>();\n        for (Map.Entry<Key<?>, Object> entry : data.entrySet()) {\n            if (entry.getValue() == null) continue;\n\n            mapped.put(entry.getKey().id(), entry.getValue());\n        }\n\n        return new LootContextImpl(mapped);\n    }\n\n    @Override\n    public boolean has(@NotNull Key<?> key) {\n        return data.containsKey(key.id());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> @Nullable T get(@NotNull Key<T> key) {\n        return (T) data.get(key.id());\n    }\n\n    @Override\n    public <T> @NotNull T get(@NotNull Key<T> key, @NotNull T defaultValue) {\n        T get = get(key);\n        return get != null ? get : defaultValue;\n    }\n\n    @Override\n    public <T> @NotNull T require(@NotNull Key<T> key) {\n        T get = get(key);\n        if (get != null) {\n            return get;\n        }\n\n        throw new NoSuchElementException(\"No value for key '\" + key + \"'\");\n    }\n}"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootEntry.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.registry.Registries;\nimport net.minestom.server.registry.RegistryKey;\nimport net.minestom.server.registry.RegistryTag;\nimport net.minestom.server.utils.Either;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Range;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * An entry in a loot table that can generate a list of {@link Choice choices} that each have their own loot and weight.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootEntry {\n    \n    @NotNull StructCodec<LootEntry> CODEC = Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends LootEntry>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, LootEntry::codec, \"type\");\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootEntry>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootEntry>> registry = DynamicRegistry.create(Key.key(\"loot_entries\"));\n        registry.register(\"alternatives\", Alternatives.CODEC);\n        registry.register(\"dynamic\", Dynamic.CODEC);\n        registry.register(\"empty\", Empty.CODEC);\n        registry.register(\"group\", Group.CODEC);\n        registry.register(\"item\", Item.CODEC);\n        registry.register(\"loot_table\", LootTable.CODEC);\n        registry.register(\"sequence\", Sequence.CODEC);\n        registry.register(\"tag\", Tag.CODEC);\n        return registry;\n    }\n\n    /**\n     * Generates any number of possible choices to choose from when generating loot.\n     * @param context the context object, to use if required\n     * @return a list, with undetermined mutability, containing the options that were generated\n     */\n    @NotNull List<Choice> requestChoices(@NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this entry\n     */\n    @NotNull StructCodec<? extends LootEntry> codec();\n\n    /**\n     * A choice, generated from an entry, that could potentially be chosen.\n     */\n    interface Choice extends LootGenerator {\n\n        /**\n         * Calculates the weight of this choice, to be used when choosing which choices should be used.\n         * This number should not be below 1.<br>\n         * When using the result of this method, be aware of the fact that it's valid for implementations of this method\n         * to return different values even when the provided context is the identical.\n         * @param context the context object, to use if required\n         * @return the weight of this choice\n         */\n        @Range(from = 1L, to = Long.MAX_VALUE) long getWeight(@NotNull LootContext context);\n\n        \n        /**\n         * A choice that uses the standard method of generating weight - adding the {@link #weight()} to the {@link #quality()}\n         * where the quality is multiplied by the provided context's luck ({@link LootContext#LUCK}).\n         */\n        interface Standard extends Choice {\n\n            /**\n             * The weight of this choice. When calculating the final weight, this value is simply added to the result.\n             * @return the base weight of this choice\n             */\n            @Range(from = 1L, to = Long.MAX_VALUE) long weight();\n\n            /**\n             * The quality of the choice. When calculating the final weight, this number is multiplied by the context's luck\n             * value, which is stored at the key {@link LootContext#LUCK}.\n             * @return the quality of the choice\n             */\n            @Range(from = 0L, to = Long.MAX_VALUE) long quality();\n\n            @Override\n            default @Range(from = 1L, to = Long.MAX_VALUE) long getWeight(@NotNull LootContext context) {\n                return Math.max(1, (long) Math.floor(weight() + quality() * context.get(LootContext.LUCK, 0d)));\n            }\n\n        }\n\n        /**\n         * A standard single choice entry that only returns itself when its conditions all succeed.\n         */\n        interface Single extends LootEntry, LootEntry.Choice, Standard {\n\n            /**\n             * @return this choice's predicates\n             */\n            @NotNull List<LootPredicate> predicates();\n\n            /**\n             * Requests choices, returning none if {@link #predicates()} are all true.\n             * {@inheritDoc}\n             */\n            @Override\n            default @NotNull List<Choice> requestChoices(@NotNull LootContext context) {\n                return LootPredicate.all(predicates(), context) ? List.of(this) : List.of();\n            }\n\n        }\n\n    }\n    \n    record Alternatives(@NotNull List<LootPredicate> predicates, @NotNull List<LootEntry> children) implements LootEntry {\n        public static final @NotNull StructCodec<Alternatives> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Alternatives::predicates,\n                \"children\", LootEntry.CODEC.list().optional(List.of()), Alternatives::children,\n                Alternatives::new\n        );\n\n        @Override\n        public @NotNull List<Choice> requestChoices(@NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return List.of();\n\n            for (var entry : this.children()) {\n                var options = entry.requestChoices(context);\n                if (!options.isEmpty()) {\n                    return options;\n                }\n            }\n            return List.of();\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Dynamic(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> functions,\n                   long weight, long quality, @NotNull Key name) implements Choice.Single {\n        public static final @NotNull StructCodec<Dynamic> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Dynamic::predicates,\n                \"functions\", LootFunction.CODEC.list().optional(List.of()), Dynamic::functions,\n                \"weight\", Codec.LONG.optional(1L), Dynamic::weight,\n                \"quality\", Codec.LONG.optional(0L), Dynamic::quality,\n                \"name\", Codec.KEY, Dynamic::name,\n                Dynamic::new\n        );\n\n        private static final net.minestom.server.tag.Tag<List<Material>> DECORATED_POT_SHERDS = net.minestom.server.tag.Tag.String(\"sherds\")\n                .map(Key::key, Key::asString)\n                .map(Material::fromKey, Material::key)\n                .list().defaultValue(List::of);\n\n        private static final net.minestom.server.tag.Tag<List<ItemStack>> CONTAINER_ITEMS = net.minestom.server.tag.Tag.ItemStack(\"Items\").list().defaultValue(List::of);\n\n        @Override\n        public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n            Block block = context.get(LootContext.BLOCK_STATE);\n            if (block == null) return List.of();\n\n            return switch (name.asString()) {\n                case \"minecraft:sherds\" -> {\n                    List<ItemStack> items = new ArrayList<>();\n                    for (Material material : block.getTag(DECORATED_POT_SHERDS)) {\n                        items.add(ItemStack.of(material));\n                    }\n                    yield items;\n                }\n                case \"minecraft:contents\" -> block.getTag(CONTAINER_ITEMS);\n                default -> List.of();\n            };\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Empty(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> functions,\n                 long weight, long quality) implements Choice.Single {\n        public static final @NotNull StructCodec<Empty> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Empty::predicates,\n                \"functions\", LootFunction.CODEC.list().optional(List.of()), Empty::functions,\n                \"weight\", Codec.LONG.optional(1L), Empty::weight,\n                \"quality\", Codec.LONG.optional(0L), Empty::quality,\n                Empty::new\n        );\n\n        @Override\n        public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n            return List.of();\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Group(@NotNull List<LootPredicate> predicates, @NotNull List<LootEntry> children) implements LootEntry {\n        public static final @NotNull StructCodec<Group> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Group::predicates,\n                \"children\", LootEntry.CODEC.list().optional(List.of()), Group::children,\n                Group::new\n        );\n\n        @Override\n        public @NotNull List<Choice> requestChoices(@NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return List.of();\n\n            List<Choice> choices = new ArrayList<>();\n            for (var entry : this.children()) {\n                choices.addAll(entry.requestChoices(context));\n            }\n            return choices;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Item(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> functions,\n                long weight, long quality, @NotNull Material name) implements Choice.Single {\n        public static final @NotNull StructCodec<Item> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Item::predicates,\n                \"functions\", LootFunction.CODEC.list().optional(List.of()), Item::functions,\n                \"weight\", Codec.LONG.optional(1L), Item::weight,\n                \"quality\", Codec.LONG.optional(0L), Item::quality,\n                \"name\", Material.CODEC, Item::name,\n                Item::new\n        );\n\n        @Override\n        public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n            return List.of(LootFunction.apply(functions, ItemStack.of(name), context));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record LootTable(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> functions,\n                     long weight, long quality, @NotNull Either<Key, net.minestom.vanilla.loot.LootTable> value) implements Choice.Single {\n        public static final @NotNull StructCodec<LootTable> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), LootTable::predicates,\n                \"functions\", LootFunction.CODEC.list().optional(List.of()), LootTable::functions,\n                \"weight\", Codec.LONG.optional(1L), LootTable::weight,\n                \"quality\", Codec.LONG.optional(0L), LootTable::quality,\n                \"value\", Codec.Either(Codec.KEY, net.minestom.vanilla.loot.LootTable.CODEC), LootTable::value,\n                LootTable::new\n        );\n\n        @Override\n        public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n            var table = switch (value) {\n                case Either.Left(Key key) -> throw new UnsupportedOperationException(\"TODO: Implement loot table registry (Key -> @Nullable LootTable)\");\n                case Either.Right(net.minestom.vanilla.loot.LootTable right) -> right;\n            };\n\n            if (table == null) return List.of();\n\n            return LootFunction.apply(functions, table.generate(context), context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Sequence(@NotNull List<LootPredicate> predicates, @NotNull List<LootEntry> children) implements LootEntry {\n        public static final @NotNull StructCodec<Sequence> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Sequence::predicates,\n                \"children\", LootEntry.CODEC.list().optional(List.of()), Sequence::children,\n                Sequence::new\n        );\n\n        @Override\n        public @NotNull List<Choice> requestChoices(@NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return List.of();\n\n            List<Choice> options = new ArrayList<>();\n            for (var entry : this.children()) {\n                var choices = entry.requestChoices(context);\n                if (choices.isEmpty()) {\n                    break;\n                }\n                options.addAll(choices);\n            }\n            return options;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n    record Tag(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> functions,\n               long weight, long quality, @NotNull RegistryTag<Material> name, boolean expand) implements Choice.Single {\n        public static final @NotNull StructCodec<Tag> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Tag::predicates,\n                \"functions\", LootFunction.CODEC.list().optional(List.of()), Tag::functions,\n                \"weight\", Codec.LONG.optional(1L), Tag::weight,\n                \"quality\", Codec.LONG.optional(0L), Tag::quality,\n                \"name\", RegistryTag.codec(Registries::material), Tag::name,\n                \"expand\", Codec.BOOLEAN, Tag::expand,\n                Tag::new\n        );\n\n        @Override\n        public @NotNull List<Choice> requestChoices(@NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) {\n                return List.of();\n            } else if (!expand) {\n                return List.of(this);\n            }\n\n            List<Choice> choices = new ArrayList<>();\n\n            for (RegistryKey<Material> key : name) {\n                Material material = MinecraftServer.process().material().get(key);\n                if (material == null) continue;\n\n                choices.add(new Choice() {\n                    @Override\n                    public @Range(from = 1L, to = Long.MAX_VALUE) long getWeight(@NotNull LootContext context) {\n                        return Tag.this.getWeight(context);\n                    }\n\n                    @Override\n                    public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n                        return List.of(ItemStack.of(material));\n                    }\n\n                });\n            }\n\n            return choices;\n        }\n\n        @Override\n        public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n            List<ItemStack> items = new ArrayList<>();\n\n            for (RegistryKey<Material> key : name) {\n                Material material = MinecraftServer.process().material().get(key);\n                if (material == null) continue;\n\n                items.add(LootFunction.apply(functions, ItemStack.of(material), context));\n            }\n\n            return items;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootEntry> codec() {\n            return CODEC;\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootFeature.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.ExperienceOrb;\nimport net.minestom.server.entity.GameMode;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.event.EventFilter;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.player.PlayerBlockBreakEvent;\nimport net.minestom.server.event.trait.InstanceEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.component.Tool;\nimport net.minestom.server.utils.time.TimeUnit;\nimport net.minestom.vanilla.datapack.Datapacks;\nimport net.minestom.vanilla.logging.Logger;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class LootFeature {\n\n    public static @NotNull Map<Key, LootTable> buildFromDatapack(@NotNull ServerProcess process) {\n        final Path tablesPath = Path.of(\"/\", \"data\", \"minecraft\", \"loot_table\");\n\n        Map<Key, LootTable> tables;\n\n        try {\n            Path jar = Datapacks.ensureCurrentJarExists();\n\n            tables = Datapacks.buildRegistryFromJar(jar, tablesPath, process, \".json\", LootTable.CODEC);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        Logger.info(\"Loaded and parsed \" + tables.size() + \" loot tables\");\n        return tables;\n    }\n\n    @SuppressWarnings(\"PatternValidation\")\n    public static @NotNull EventNode<InstanceEvent> createEventNode(@NotNull Map<Key, LootTable> tables) {\n        return EventNode.type(\"vri:loot\", EventFilter.INSTANCE).addListener(PlayerBlockBreakEvent.class, event -> {\n            if (event.getPlayer().getGameMode() == GameMode.CREATIVE) return; // No loot in creative mode\n\n            final Block block = event.getBlock();\n\n            ItemStack heldItem = event.getPlayer().getItemInMainHand();\n            Tool tool = heldItem.get(DataComponents.TOOL);\n\n            // If the block doesn't require a tool, OR there is a tool and the block is explicitly allowed\n            boolean canDrop = !block.registry().requiresTool() || (tool != null && tool.isCorrectForDrops(block));\n\n            if (!canDrop) return;\n\n            // TODO: Can be pre-converted to `LootTable[]` that turns block IDs into loot tables.\n            Key key = Key.key(\"blocks/\" + block.key().value());\n            LootTable table = tables.get(key);\n\n            if (table == null) {\n                Logger.warn(\"Block \" + block.key() + \" does not have a corresponding loot table (would be at: \" + key.asString() + \")\");\n                return;\n            }\n\n            // Build a context and drop\n            LootContext context = LootContext.from(Map.of(\n                    LootContext.RANDOM, new Random(), // TODO: Replace with sequence random\n                    LootContext.WORLD, event.getInstance(),\n                    LootContext.BLOCK_STATE, block,\n                    LootContext.ORIGIN, event.getBlockPosition(),\n                    LootContext.TOOL, heldItem,\n                    LootContext.THIS_ENTITY, event.getPlayer()\n            ));\n\n            for (ItemStack drop : table.generate(context)) {\n                blockDrop(event.getInstance(), drop, event.getBlockPosition());\n            }\n\n            int experience = BlockExperience.getExperience(block, heldItem, ThreadLocalRandom.current());\n            if (experience != 0) {\n                ExperienceOrb orb = new ExperienceOrb((short) experience);\n                orb.setInstance(event.getInstance(), event.getBlockPosition().add(0.5, 0.5, 0.5));\n            }\n        });\n    }\n\n    public static void blockDrop(@NotNull Instance instance, @NotNull ItemStack item, @NotNull Point block) {\n        ThreadLocalRandom rng = ThreadLocalRandom.current();\n\n        Pos spawn = new Pos(\n                block.blockX() + 0.5 + rng.nextDouble(-0.25, 0.25),\n                block.blockY() + 0.5 + rng.nextDouble(-0.25, 0.25) - EntityType.ITEM.height() / 2,\n                block.blockZ() + 0.5 + rng.nextDouble(-0.25, 0.25),\n                rng.nextFloat(360),\n                0\n        );\n\n        drop(instance, item, spawn);\n    }\n\n    public static void drop(@NotNull Instance instance, @NotNull ItemStack item, @NotNull Point position) {\n        ItemEntity entity = new ItemEntity(item);\n\n        ThreadLocalRandom rng = ThreadLocalRandom.current();\n\n        Vec vel = new Vec(\n                rng.nextDouble(-0.1, 0.1),\n                0.2,\n                rng.nextDouble(-0.1, 0.1)\n        ).mul(20);\n\n        entity.setPickupDelay(10, TimeUnit.SERVER_TICK);\n\n        entity.setInstance(instance, position);\n        entity.setVelocity(vel);\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootFunction.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.*;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.util.RGBLike;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.ServerFlag;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.Result;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.codec.Transcoder;\nimport net.minestom.server.color.Color;\nimport net.minestom.server.component.DataComponent;\nimport net.minestom.server.component.DataComponentMap;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.*;\nimport net.minestom.server.entity.attribute.Attribute;\nimport net.minestom.server.entity.attribute.AttributeModifier;\nimport net.minestom.server.entity.attribute.AttributeOperation;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.book.FilteredText;\nimport net.minestom.server.item.component.*;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.item.instrument.Instrument;\nimport net.minestom.server.potion.PotionEffect;\nimport net.minestom.server.potion.PotionType;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.registry.Registries;\nimport net.minestom.server.registry.RegistryKey;\nimport net.minestom.server.registry.RegistryTag;\nimport net.minestom.server.tag.Tag;\nimport net.minestom.server.utils.Either;\nimport net.minestom.vanilla.loot.util.*;\nimport net.minestom.vanilla.loot.util.nbt.NBTPath;\nimport net.minestom.vanilla.loot.util.nbt.NBTReference;\nimport net.minestom.vanilla.loot.util.nbt.NBTUtils;\nimport net.minestom.vanilla.loot.util.predicate.ItemPredicate;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * A function that allows loot to pass through it, potentially making modifications.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootFunction {\n\n    @NotNull Codec<LootFunction> CODEC = makeCodec().orElse(makeCodec().list().transform(Sequence::new, seq -> ((Sequence) seq).functions()));\n\n    private static StructCodec<LootFunction> makeCodec() {\n        return Codec.RegistryTaggedUnion(registries -> {\n            class Holder {\n                static final @NotNull DynamicRegistry<StructCodec<? extends LootFunction>> CODEC = createDefaultRegistry();\n            }\n            return Holder.CODEC;\n        }, LootFunction::codec, \"function\");\n    }\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootFunction>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootFunction>> registry = DynamicRegistry.create(Key.key(\"loot_functions\"));\n        registry.register(\"apply_bonus\", ApplyBonus.CODEC);\n        registry.register(\"copy_components\", CopyComponents.CODEC);\n        registry.register(\"copy_custom_data\", CopyCustomData.CODEC);\n        registry.register(\"copy_name\", CopyName.CODEC);\n        registry.register(\"copy_state\", CopyState.CODEC);\n        registry.register(\"enchanted_count_increase\", EnchantedCountIncrease.CODEC);\n        registry.register(\"enchant_randomly\", EnchantRandomly.CODEC);\n        registry.register(\"enchant_with_levels\", EnchantWithLevels.CODEC);\n        registry.register(\"exploration_map\", ExplorationMap.CODEC);\n        registry.register(\"explosion_decay\", ExplosionDecay.CODEC);\n        registry.register(\"fill_player_head\", FillPlayerHead.CODEC);\n        registry.register(\"filtered\", Filtered.CODEC);\n        registry.register(\"furnace_smelt\", FurnaceSmelt.CODEC);\n        registry.register(\"limit_count\", LimitCount.CODEC);\n        registry.register(\"modify_contents\", ModifyContents.CODEC);\n        registry.register(\"reference\", Reference.CODEC);\n        registry.register(\"set_attributes\", SetAttributes.CODEC);\n        registry.register(\"set_banner_pattern\", SetBannerPattern.CODEC);\n        registry.register(\"set_book_cover\", SetBookCover.CODEC);\n        registry.register(\"set_components\", SetComponents.CODEC);\n        registry.register(\"set_contents\", SetContents.CODEC);\n        registry.register(\"set_count\", SetCount.CODEC);\n        registry.register(\"set_custom_data\", SetCustomData.CODEC);\n        registry.register(\"set_custom_model_data\", SetCustomModelData.CODEC);\n        registry.register(\"set_damage\", SetDamage.CODEC);\n        registry.register(\"set_enchantments\", SetEnchantments.CODEC);\n        registry.register(\"set_firework_explosion\", SetFireworkExplosion.CODEC);\n        registry.register(\"set_fireworks\", SetFireworks.CODEC);\n        registry.register(\"set_instrument\", SetInstrument.CODEC);\n        registry.register(\"set_item\", SetItem.CODEC);\n        registry.register(\"set_loot_table\", SetLootTable.CODEC);\n        registry.register(\"set_lore\", SetLore.CODEC);\n        registry.register(\"set_name\", SetName.CODEC);\n        registry.register(\"set_ominous_bottle_amplifier\", SetOminousBottleAmplifier.CODEC);\n        registry.register(\"set_potion\", SetPotion.CODEC);\n        registry.register(\"set_stew_effect\", SetStewEffect.CODEC);\n        registry.register(\"set_writable_book_pages\", SetWritableBookPages.CODEC);\n        registry.register(\"set_written_book_pages\", SetWrittenBookPages.CODEC);\n        registry.register(\"sequence\", Sequence.CODEC);\n        registry.register(\"toggle_tooltips\", ToggleTooltips.CODEC);\n        return registry;\n    }\n\n    /**\n     * Performs any mutations on the provided object and returns the result.\n     * @param input the input item to this function\n     * @param context the context object, to use if required\n     * @return the modified form of the input\n     */\n    @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this function\n     */\n    @NotNull StructCodec<? extends LootFunction> codec();\n\n    /**\n     * Applies each function to the given item consecutively.\n     * @param functions the functions to apply\n     * @param item the item to modify\n     * @param context the context to use\n     * @return the modified item\n     */\n    static @NotNull ItemStack apply(@NotNull Collection<LootFunction> functions, @NotNull ItemStack item, @NotNull LootContext context) {\n        for (LootFunction function : functions) {\n            item = function.apply(item, context);\n        }\n        return item;\n    }\n\n    /**\n     * Applies each function to each of the given items consecutively.\n     * @param functions the functions to apply\n     * @param items the items to modify\n     * @param context the context to use\n     * @return the modified items\n     */\n    static @NotNull List<ItemStack> apply(@NotNull Collection<LootFunction> functions, @NotNull List<ItemStack> items, @NotNull LootContext context) {\n        List<ItemStack> newItems = new ArrayList<>(items.size());\n        for (ItemStack item : items) {\n            newItems.add(LootFunction.apply(functions, item, context));\n        }\n        return newItems;\n    }\n\n    record ApplyBonus(@NotNull List<LootPredicate> predicates, @NotNull RegistryKey<Enchantment> enchantment, @NotNull Formula.Wrapper formula) implements LootFunction {\n        public static final @NotNull StructCodec<ApplyBonus> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), ApplyBonus::predicates,\n                \"enchantment\", RegistryKey.codec(Registries::enchantment), ApplyBonus::enchantment,\n                StructCodec.INLINE, Formula.CODEC, ApplyBonus::formula,\n                ApplyBonus::new\n        );\n\n        public sealed interface Formula {\n\n            enum FormulaType {\n                BINOMIAL_WITH_BONUS_COUNT,\n                ORE_DROPS,\n                UNIFORM_BONUS_COUNT,\n            }\n\n            record Wrapper(Formula parameters) {\n                @SuppressWarnings(\"unchecked\")\n                private static <T extends Formula> StructCodec<Wrapper> wrap(@NotNull StructCodec<T> codec) {\n                    return StructCodec.struct(\n                            \"parameters\", codec.transform(a->a,a->(T)a), Wrapper::parameters,\n                            Wrapper::new\n                    );\n                }\n\n                private static final StructCodec<Wrapper> BINOMIAL_WITH_BONUS_COUNT = wrap(BinomialWithBonusCount.CODEC);\n                private static final StructCodec<Wrapper> ORE_DROPS = new StructCodec<>() {\n                    @Override\n                    public @NotNull <D> Result<Wrapper> decodeFromMap(@NotNull Transcoder<D> coder, Transcoder.@NotNull MapLike<D> map) {\n                        return new Result.Ok<>(new Wrapper(new OreDrops()));\n                    }\n\n                    @Override\n                    public @NotNull <D> Result<D> encodeToMap(@NotNull Transcoder<D> coder, @NotNull Wrapper value, Transcoder.@NotNull MapBuilder<D> map) {\n                        return new Result.Ok<>(map.build());\n                    }\n                };\n                private static final StructCodec<Wrapper> UNIFORM_BONUS_COUNT = wrap(UniformBonusCount.CODEC);\n\n                public static @NotNull StructCodec<Wrapper> codec(@NotNull FormulaType type) {\n                    return switch (type) {\n                        case BINOMIAL_WITH_BONUS_COUNT -> BINOMIAL_WITH_BONUS_COUNT;\n                        case ORE_DROPS -> ORE_DROPS;\n                        case UNIFORM_BONUS_COUNT -> UNIFORM_BONUS_COUNT;\n                    };\n                }\n            }\n\n            @NotNull Codec<Wrapper> CODEC = Codec.KEY.transform(key -> switch (key.asString()) {\n                case \"minecraft:binomial_with_bonus_count\" -> FormulaType.BINOMIAL_WITH_BONUS_COUNT;\n                case \"minecraft:ore_drops\" -> FormulaType.ORE_DROPS;\n                case \"minecraft:uniform_bonus_count\" -> FormulaType.UNIFORM_BONUS_COUNT;\n                default -> throw new IllegalArgumentException();\n            }, type -> Key.key(type.toString().toLowerCase())).unionType(\"formula\", Wrapper::codec, (Wrapper formula) -> switch (formula.parameters()) {\n                case BinomialWithBonusCount ignored -> FormulaType.BINOMIAL_WITH_BONUS_COUNT;\n                case OreDrops ignored -> FormulaType.ORE_DROPS;\n                case UniformBonusCount ignored -> FormulaType.UNIFORM_BONUS_COUNT;\n            });\n\n            int calculate(@NotNull Random random, int count, int level);\n\n            record UniformBonusCount(int bonusMultiplier) implements Formula {\n                public static final @NotNull StructCodec<UniformBonusCount> CODEC = StructCodec.struct(\n                        \"bonusMultiplier\", StructCodec.INT, UniformBonusCount::bonusMultiplier,\n                        UniformBonusCount::new\n                );\n\n                @Override\n                public int calculate(@NotNull Random random, int count, int level) {\n                    return count + random.nextInt(bonusMultiplier * level + 1);\n                }\n            }\n\n            record OreDrops() implements Formula {\n                public static final @NotNull StructCodec<OreDrops> CODEC = StructCodec.struct(OreDrops::new);\n\n                @Override\n                public int calculate(@NotNull Random random, int count, int level) {\n                    if (level <= 0) return count;\n\n                    return count * Math.max(1, random.nextInt(level + 2));\n                }\n            }\n\n            record BinomialWithBonusCount(float probability, int extra) implements Formula {\n                public static final @NotNull StructCodec<BinomialWithBonusCount> CODEC = StructCodec.struct(\n                        \"probability\", Codec.FLOAT, BinomialWithBonusCount::probability,\n                        \"extra\", Codec.INT, BinomialWithBonusCount::extra,\n                        BinomialWithBonusCount::new\n                );\n\n                @Override\n                public int calculate(@NotNull Random random, int count, int level) {\n                    for (int i = 0; i < extra + level; i++) {\n                        if (random.nextFloat() < probability) {\n                            count++;\n                        }\n                    }\n\n                    return count;\n                }\n            }\n\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            ItemStack tool = context.get(LootContext.TOOL);\n            if (tool == null) return input;\n\n            int level = EnchantmentUtils.level(tool, enchantment);\n            int newCount = formula.parameters().calculate(context.require(LootContext.RANDOM), input.amount(), level);\n\n            return input.withAmount(newCount);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record CopyComponents(@NotNull List<LootPredicate> predicates, @NotNull RelevantTarget source,\n                          @Nullable List<DataComponent<?>> include, @Nullable List<DataComponent<?>> exclude) implements LootFunction {\n        public static final @NotNull StructCodec<CopyComponents> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), CopyComponents::predicates,\n                \"source\", RelevantTarget.CODEC, CopyComponents::source,\n                \"include\", Codec.KEY.<DataComponent<?>>transform(DataComponent::fromKey, DataComponent::key).list().optional(), CopyComponents::include,\n                \"exclude\", Codec.KEY.<DataComponent<?>>transform(DataComponent::fromKey, DataComponent::key).list().optional(), CopyComponents::exclude,\n                CopyComponents::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            // TODO: Incomplete\n            throw new UnsupportedOperationException(\"TODO: Implement Tag<DataComponentMap> for blocks.\");\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record CopyCustomData(@NotNull List<LootPredicate> predicates, @NotNull LootNBT source, @NotNull List<Operation> ops) implements LootFunction {\n        public static final @NotNull StructCodec<CopyCustomData> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), CopyCustomData::predicates,\n                \"source\", LootNBT.CODEC, CopyCustomData::source,\n                \"ops\", Operation.CODEC.list(), CopyCustomData::ops,\n                CopyCustomData::new\n        );\n\n        public record Operation(@NotNull NBTPath source, @NotNull NBTPath target, @NotNull Operator op) {\n            public static final @NotNull StructCodec<Operation> CODEC = StructCodec.struct(\n                    \"source\", NBTPath.CODEC, Operation::source,\n                    \"target\", NBTPath.CODEC, Operation::target,\n                    \"op\", Operator.SERIALIZER, Operation::op,\n                    Operation::new\n            );\n\n            public void execute(@NotNull NBTReference nbt, @NotNull BinaryTag sourceTag) {\n                List<BinaryTag> nbts = new ArrayList<>();\n                source.get(sourceTag).forEach(ref -> nbts.add(ref.get()));\n\n                if (nbts.isEmpty()) return;\n                op.merge(nbt, target, nbts);\n            }\n        }\n\n        public enum Operator {\n            REPLACE() {\n                @Override\n                public void merge(@NotNull NBTReference nbt, @NotNull NBTPath target, @NotNull List<BinaryTag> source) {\n                    target.set(nbt, source.getLast());\n                }\n            },\n            APPEND() {\n                @Override\n                public void merge(@NotNull NBTReference nbt, @NotNull NBTPath target, @NotNull List<BinaryTag> source) {\n                    List<NBTReference> nbts = target.getWithDefaults(nbt, ListBinaryTag::empty);\n\n                    for (var ref : nbts) {\n                        source.forEach(ref::listAdd);\n                    }\n                }\n            },\n            MERGE() {\n                @Override\n                public void merge(@NotNull NBTReference nbt, @NotNull NBTPath target, @NotNull List<BinaryTag> source) {\n                    List<NBTReference> nbts = target.getWithDefaults(nbt, CompoundBinaryTag::empty);\n\n                    for (var ref : nbts) {\n                        if (ref.get() instanceof CompoundBinaryTag compound) {\n                            for (var nbt2 : source) {\n                                if (nbt2 instanceof CompoundBinaryTag compound2) {\n                                    ref.set(NBTUtils.merge(compound, compound2));\n                                }\n                            }\n                        }\n                    }\n                }\n            };\n\n            public static final @NotNull Codec<Operator> SERIALIZER = Codec.Enum(Operator.class);\n\n            public abstract void merge(@NotNull NBTReference nbt, @NotNull NBTPath target, @NotNull List<BinaryTag> source);\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            BinaryTag sourceNBT = source.getNBT(context);\n            if (sourceNBT == null) return input;\n\n            NBTReference targetNBT = NBTReference.of(input.get(DataComponents.CUSTOM_DATA, CustomData.EMPTY).nbt());\n\n            for (Operation operation : ops) {\n                operation.execute(targetNBT, sourceNBT);\n            }\n\n            if (targetNBT.get() instanceof CompoundBinaryTag compound) {\n                return input.with(DataComponents.CUSTOM_DATA, new CustomData(compound));\n            } else {\n                return input;\n            }\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record CopyName(@NotNull List<LootPredicate> predicates, @NotNull RelevantTarget source) implements LootFunction {\n        public static final @NotNull StructCodec<CopyName> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), CopyName::predicates,\n                \"source\", RelevantTarget.CODEC, CopyName::source,\n                CopyName::new\n        );\n\n        private static final Tag<Component> CUSTOM_NAME = Tag.Component(\"CustomName\");\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            Object key = context.get(source.key());\n\n            Component customName;\n            if (key instanceof Entity entity && entity.getCustomName() != null) {\n                customName = entity.getCustomName();\n            } else if (key instanceof Block block && block.hasTag(CUSTOM_NAME)) {\n                customName = block.getTag(CUSTOM_NAME);\n            } else {\n                return input;\n            }\n\n            return input.with(DataComponents.CUSTOM_NAME, customName);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record CopyState(@NotNull List<LootPredicate> predicates, @NotNull Block block, @NotNull List<String> properties) implements LootFunction {\n        public static final @NotNull StructCodec<CopyState> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), CopyState::predicates,\n                \"block\", Codec.KEY.transform(Block::fromKey, Block::key), CopyState::block,\n                \"properties\", Codec.STRING.list(), CopyState::properties,\n                CopyState::new\n        );\n\n        public CopyState {\n            List<String> props = new ArrayList<>(properties);\n            props.removeIf(name -> !block.properties().containsKey(name));\n            properties = List.copyOf(props);\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            Block block = context.get(LootContext.BLOCK_STATE);\n            if (block == null) return input;\n\n            ItemBlockState irritableBowelSyndrome = input.get(DataComponents.BLOCK_STATE, ItemBlockState.EMPTY);\n\n            if (!block.key().equals(this.block.key())) return input;\n\n            for (var prop : properties) {\n                @Nullable String value = block.getProperty(prop);\n                if (value == null) continue;\n\n                irritableBowelSyndrome = irritableBowelSyndrome.with(prop, value);\n            }\n\n            return input.with(DataComponents.BLOCK_STATE, irritableBowelSyndrome);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record EnchantedCountIncrease(@NotNull List<LootPredicate> predicates, @NotNull RegistryKey<Enchantment> enchantment,\n                                  @NotNull LootNumber count, @Nullable Integer limit) implements LootFunction {\n        public static final @NotNull StructCodec<EnchantedCountIncrease> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), EnchantedCountIncrease::predicates,\n                \"enchantment\", RegistryKey.codec(Registries::enchantment), EnchantedCountIncrease::enchantment,\n                \"count\", LootNumber.CODEC, EnchantedCountIncrease::count,\n                \"limit\", Codec.INT.optional(), EnchantedCountIncrease::limit,\n                EnchantedCountIncrease::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            Entity attacker = context.get(LootContext.ATTACKING_ENTITY);\n            int level = EnchantmentUtils.level(attacker, enchantment);\n\n            if (level == 0) return input;\n\n            int newAmount = input.amount() + level * count.getInt(context);\n\n            return input.withAmount(limit != null ? Math.min(limit, newAmount) : newAmount);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record EnchantRandomly(@NotNull List<LootPredicate> predicates, @Nullable RegistryTag<Enchantment> options, boolean onlyCompatible) implements LootFunction {\n        public static final @NotNull StructCodec<EnchantRandomly> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), EnchantRandomly::predicates,\n                \"options\", RegistryTag.codec(Registries::enchantment).optional(), EnchantRandomly::options,\n                \"only_compatible\", Codec.BOOLEAN.optional(true), EnchantRandomly::onlyCompatible,\n                EnchantRandomly::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            var reg = MinecraftServer.getEnchantmentRegistry();\n\n            List<RegistryKey<Enchantment>> values = new ArrayList<>();\n            (options == null ? reg.keys() : options).forEach(values::add);\n\n            if (onlyCompatible && !input.material().equals(Material.BOOK)) {\n                values.removeIf(ench -> !reg.get(ench).supportedItems().contains(input.material()));\n            }\n\n            if (values.isEmpty()) return input;\n\n            Random rng = context.require(LootContext.RANDOM);\n\n            RegistryKey<Enchantment> chosen = values.get(rng.nextInt(values.size()));\n\n            int level = rng.nextInt(reg.get(chosen).maxLevel() + 1);\n\n            return EnchantmentUtils.modifyItem(input, map -> map.put(chosen, level));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record EnchantWithLevels(@NotNull List<LootPredicate> predicates, @NotNull LootNumber levels, @Nullable RegistryTag<Enchantment> options) implements LootFunction {\n        public static final @NotNull StructCodec<EnchantWithLevels> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), EnchantWithLevels::predicates,\n                \"levels\", LootNumber.CODEC, EnchantWithLevels::levels,\n                \"options\", RegistryTag.codec(Registries::enchantment).optional(), EnchantWithLevels::options,\n                EnchantWithLevels::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement enchanting (Random, ItemStack, int levels, @Nullable RegistryTag<Enchantment> options -> ItemStack)\");\n            return null;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record ExplorationMap(@NotNull List<LootPredicate> predicates) implements LootFunction {\n        public static final @NotNull StructCodec<ExplorationMap> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), ExplorationMap::predicates,\n                ExplorationMap::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            // TODO: Incomplete\n            throw new UnsupportedOperationException(\"TODO: Implement ExplorationMap functionality and serialization\");\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record ExplosionDecay(@NotNull List<LootPredicate> predicates) implements LootFunction {\n        public static final @NotNull StructCodec<ExplosionDecay> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), ExplosionDecay::predicates,\n                ExplosionDecay::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            Float radius = context.get(LootContext.EXPLOSION_RADIUS);\n            if (radius == null) return input;\n\n            Random random = context.require(LootContext.RANDOM);\n\n            float chance = 1 / radius;\n            int trials = input.amount();\n\n            int newAmount = 0;\n\n            for (int i = 0; i < trials; i++) {\n                if (random.nextFloat() <= chance) {\n                    newAmount++;\n                }\n            }\n\n            return input.withAmount(newAmount);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record FillPlayerHead(@NotNull List<LootPredicate> predicates, @NotNull RelevantEntity entity) implements LootFunction {\n        public static final @NotNull StructCodec<FillPlayerHead> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), FillPlayerHead::predicates,\n                \"entity\", RelevantEntity.CODEC, FillPlayerHead::entity,\n                FillPlayerHead::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (!input.material().equals(Material.PLAYER_HEAD)) return input;\n\n            if (!(context.get(entity.key()) instanceof Player player)) return input;\n\n            PlayerSkin skin = player.getSkin();\n            if (skin == null) return input;\n\n            return input.with(DataComponents.PROFILE, new HeadProfile(skin));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record Filtered(@NotNull List<LootPredicate> predicates, @NotNull ItemPredicate predicate, @NotNull LootFunction modifier) implements LootFunction {\n        public static final @NotNull StructCodec<Filtered> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Filtered::predicates,\n                \"item_filter\", ItemPredicate.CODEC, Filtered::predicate,\n                \"modifier\", LootFunction.CODEC, Filtered::modifier,\n                Filtered::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            return LootPredicate.all(predicates, context) && predicate.test(input, context) ?\n                    modifier.apply(input, context) : input;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record FurnaceSmelt(@NotNull List<LootPredicate> predicates) implements LootFunction {\n        public static final @NotNull StructCodec<FurnaceSmelt> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), FurnaceSmelt::predicates,\n                FurnaceSmelt::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement smelting (ItemStack -> ItemStack)\");\n            ItemStack smelted = null;\n\n            return smelted != null ? smelted.withAmount(input.amount()) : input;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record LimitCount(@NotNull List<LootPredicate> predicates, @NotNull LootNumberRange limit) implements LootFunction {\n        public static final @NotNull StructCodec<LimitCount> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), LimitCount::predicates,\n                \"limit\", LootNumberRange.CODEC, LimitCount::limit,\n                LimitCount::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n            return input.withAmount(i -> (int) limit.limit(context, i));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record ModifyContents(@NotNull List<LootPredicate> predicates, @NotNull List<LootFunction> modifier,\n                          @NotNull DataComponent<List<ItemStack>> component) implements LootFunction {\n        private static final @NotNull Map<Key, DataComponent<List<ItemStack>>> NAMED_CONTAINERS =\n                Stream.of(DataComponents.CONTAINER, DataComponents.BUNDLE_CONTENTS, DataComponents.CHARGED_PROJECTILES)\n                        .collect(Collectors.toMap(DataComponent::key, Function.identity()));\n\n        private static final @NotNull Codec<DataComponent<List<ItemStack>>> CONTAINER = Codec.KEY.transform(NAMED_CONTAINERS::get, DataComponent::key);\n\n        public static final @NotNull StructCodec<ModifyContents> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), ModifyContents::predicates,\n                \"modifier\", LootFunction.CODEC.list(), ModifyContents::modifier,\n                \"component\", CONTAINER, ModifyContents::component,\n                ModifyContents::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            List<ItemStack> items = input.get(component);\n            if (items == null) return input;\n\n            List<ItemStack> updated = new ArrayList<>();\n            for (ItemStack item : items) {\n                updated.add(LootFunction.apply(modifier, item, context));\n            }\n\n            return input.with(component, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record Reference(@NotNull List<LootPredicate> predicates, @NotNull Key name) implements LootFunction {\n        public static final @NotNull StructCodec<Reference> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), Reference::predicates,\n                \"name\", Codec.KEY, Reference::name,\n                Reference::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement loot function registry (Key -> @Nullable LootFunction)\");\n            LootFunction function = null;\n\n            return function != null ? function.apply(input, context) : input;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetAttributes(@NotNull List<LootPredicate> predicates, @NotNull List<AttributeDirective> modifiers, boolean replace) implements LootFunction {\n        public static final @NotNull StructCodec<SetAttributes> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetAttributes::predicates,\n                \"modifiers\", AttributeDirective.CODEC.list(), SetAttributes::modifiers,\n                \"replace\", Codec.BOOLEAN.optional(true), SetAttributes::replace,\n                SetAttributes::new\n        );\n\n        public record AttributeDirective(@NotNull Key id, @NotNull Attribute attribute, @NotNull AttributeOperation operation,\n                                         @NotNull LootNumber amount, @NotNull List<EquipmentSlot> slots) {\n\n            public static final @NotNull Codec<EquipmentSlot> CUSTOM_SLOT;\n\n            static {\n                Function<EquipmentSlot, String> name = slot -> slot.name().toLowerCase(Locale.ROOT).replace(\"_\", \"\");\n\n                Map<String, EquipmentSlot> named = Stream.of(EquipmentSlot.values()).collect(Collectors.toMap(name, Function.identity()));\n\n                CUSTOM_SLOT = Codec.STRING.transform(string -> Objects.requireNonNull(named.get(string)), name::apply);\n            }\n\n            public static final @NotNull Codec<AttributeDirective> CODEC = StructCodec.struct(\n                    \"id\", Codec.KEY, AttributeDirective::id,\n                    \"attribute\", Attribute.CODEC, AttributeDirective::attribute,\n                    \"operation\", AttributeOperation.CODEC, AttributeDirective::operation,\n                    \"amount\", LootNumber.CODEC, AttributeDirective::amount,\n                    \"slot\", CUSTOM_SLOT.listOrSingle(Integer.MAX_VALUE), AttributeDirective::slots,\n                    AttributeDirective::new\n            );\n\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            var component = input.get(DataComponents.ATTRIBUTE_MODIFIERS, AttributeList.EMPTY);\n\n            List<AttributeList.Modifier> list = replace ? new ArrayList<>() : new ArrayList<>(component.modifiers());\n\n            for (var modifier : modifiers) {\n                if (modifier.slots().isEmpty()) continue;\n\n                AttributeModifier mod = new AttributeModifier(\n                        modifier.id(),\n                        modifier.amount().getDouble(context),\n                        modifier.operation()\n                );\n\n                EquipmentSlot slot = modifier.slots().get(context.require(LootContext.RANDOM).nextInt(modifier.slots().size()));\n\n                EquipmentSlotGroup group = switch (slot) {\n                    case MAIN_HAND -> EquipmentSlotGroup.MAIN_HAND;\n                    case OFF_HAND -> EquipmentSlotGroup.OFF_HAND;\n                    case BOOTS -> EquipmentSlotGroup.FEET;\n                    case LEGGINGS -> EquipmentSlotGroup.LEGS;\n                    case CHESTPLATE -> EquipmentSlotGroup.CHEST;\n                    case HELMET -> EquipmentSlotGroup.HEAD;\n                    case BODY -> EquipmentSlotGroup.BODY;\n                    case SADDLE -> EquipmentSlotGroup.SADDLE;\n                };\n\n                list.add(new AttributeList.Modifier(modifier.attribute(), mod, group));\n            }\n\n            return input.with(DataComponents.ATTRIBUTE_MODIFIERS, new AttributeList(list));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetBannerPattern(@NotNull List<LootPredicate> predicates, @NotNull BannerPatterns patterns, boolean append) implements LootFunction {\n        public static final @NotNull StructCodec<SetBannerPattern> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetBannerPattern::predicates,\n                \"patterns\", BannerPatterns.CODEC, SetBannerPattern::patterns,\n                \"append\", Codec.BOOLEAN, SetBannerPattern::append,\n                SetBannerPattern::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (append) {\n                BannerPatterns patterns = input.get(DataComponents.BANNER_PATTERNS);\n                if (patterns != null) {\n                    List<BannerPatterns.Layer> layers = new ArrayList<>(patterns.layers());\n                    layers.addAll(this.patterns().layers());\n                    return input.with(DataComponents.BANNER_PATTERNS, new BannerPatterns(layers));\n                }\n            }\n\n            return input.with(DataComponents.BANNER_PATTERNS, patterns);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetBookCover(@NotNull List<LootPredicate> predicates, @Nullable FilteredText<String> title,\n                        @Nullable String author, @Nullable Integer generation) implements LootFunction {\n        public static final @NotNull StructCodec<SetBookCover> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetBookCover::predicates,\n                \"title\", FilteredText.STRING_CODEC.optional(), SetBookCover::title,\n                \"author\", Codec.STRING.optional(), SetBookCover::author,\n                \"generation\", Codec.INT.optional(), SetBookCover::generation,\n                SetBookCover::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            WrittenBookContent content = input.get(DataComponents.WRITTEN_BOOK_CONTENT, WrittenBookContent.EMPTY);\n\n            WrittenBookContent updated = new WrittenBookContent(\n                    title != null ? title : content.title(),\n                    author != null ? author : content.author(),\n                    generation != null ? generation : content.generation(),\n                    content.pages(),\n                    content.resolved()\n            );\n\n            return input.with(DataComponents.WRITTEN_BOOK_CONTENT, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetComponents(@NotNull List<LootPredicate> predicates, @NotNull DataComponentMap changes) implements LootFunction {\n        public static final @NotNull StructCodec<SetComponents> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetComponents::predicates,\n                \"components\", DataComponent.PATCH_CODEC, SetComponents::changes,\n                SetComponents::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            ItemStack.Builder builder = input.builder();\n\n            // This and .constantGeneric are hacks for until there exists a way to apply a patch to an item\n            for (DataComponent.Value entry : changes.entrySet()) {\n                constantGeneric(builder, entry.component(), entry.value());\n            }\n\n            return builder.build();\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        private static <T> void constantGeneric(@NotNull ItemStack.Builder builder, @NotNull DataComponent<T> key, Object value) {\n            builder.set(key, (T) value);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetContents(@NotNull List<LootPredicate> predicates, @NotNull List<LootEntry> entries,\n                       @NotNull DataComponent<List<ItemStack>> type) implements LootFunction {\n        public static final @NotNull StructCodec<SetContents> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetContents::predicates,\n                \"modifier\", LootEntry.CODEC.list(), SetContents::entries,\n                \"type\", ModifyContents.CONTAINER, SetContents::type,\n                SetContents::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            List<ItemStack> contents = new ArrayList<>();\n\n            for (LootEntry entry : entries) {\n                for (LootEntry.Choice choice : entry.requestChoices(context)) {\n                    contents.addAll(choice.generate(context));\n                }\n            }\n\n            return input.with(type, contents);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetCount(@NotNull List<LootPredicate> predicates, @NotNull LootNumber count, boolean add) implements LootFunction {\n        public static final @NotNull StructCodec<SetCount> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetCount::predicates,\n                \"count\", LootNumber.CODEC, SetCount::count,\n                \"add\", Codec.BOOLEAN.optional(false), SetCount::add,\n                SetCount::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n            return input.withAmount(amount -> (this.add ? amount : 0) + this.count.getInt(context));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetCustomData(@NotNull List<LootPredicate> predicates, @NotNull CompoundBinaryTag tag) implements LootFunction {\n        public static final @NotNull StructCodec<SetCustomData> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetCustomData::predicates,\n                \"tag\", Codec.NBT_COMPOUND, SetCustomData::tag,\n                SetCustomData::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            return input.with(DataComponents.CUSTOM_DATA, new CustomData(tag));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetCustomModelData(@NotNull List<LootPredicate> predicates, @NotNull LootNumber value) implements LootFunction {\n        public static final @NotNull StructCodec<SetCustomModelData> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetCustomModelData::predicates,\n                \"value\", LootNumber.CODEC, SetCustomModelData::value,\n                SetCustomModelData::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            return input;\n            // TODO: Incomplete\n//            return input.with(ItemComponent.CUSTOM_MODEL_DATA, new CustomModelData(value.getInt(context));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetDamage(@NotNull List<LootPredicate> predicates, @NotNull LootNumber damage, boolean add) implements LootFunction {\n        public static final @NotNull StructCodec<SetDamage> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetDamage::predicates,\n                \"damage\", LootNumber.CODEC, SetDamage::damage,\n                \"add\", Codec.BOOLEAN.optional(false), SetDamage::add,\n                SetDamage::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            int maxDamage = input.get(DataComponents.MAX_DAMAGE, -1);\n            if (maxDamage == -1) return input;\n\n            double damage = input.get(DataComponents.DAMAGE, 0) / (double) maxDamage;\n\n            double currentDura = add ? 1 - damage : 0;\n            double newDura = Math.max(0, Math.min(1, currentDura + this.damage.getDouble(context)));\n\n            double newDamage = 1 - newDura;\n\n            return input.with(DataComponents.DAMAGE, (int) Math.floor(newDamage * maxDamage));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetEnchantments(@NotNull List<LootPredicate> predicates, @NotNull Map<RegistryKey<Enchantment>, LootNumber> enchantments, boolean add) implements LootFunction {\n        public static final @NotNull StructCodec<SetEnchantments> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetEnchantments::predicates,\n                \"enchantments\", RegistryKey.codec(Registries::enchantment).mapValue(LootNumber.CODEC), SetEnchantments::enchantments,\n                \"add\", Codec.BOOLEAN.optional(false), SetEnchantments::add,\n                SetEnchantments::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            return EnchantmentUtils.modifyItem(input, map -> {\n                this.enchantments.forEach((enchantment, number) -> {\n                    int count = number.getInt(context);\n                    if (add) {\n                        count += map.getOrDefault(enchantment, 0);\n                    }\n                    map.put(enchantment, count);\n                });\n            });\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetFireworkExplosion(@NotNull List<LootPredicate> predicates, @Nullable FireworkExplosion.Shape shape,\n                                @Nullable List<RGBLike> colors, @Nullable List<RGBLike> fadeColors,\n                                @Nullable Boolean trail, @Nullable Boolean twinkle) implements LootFunction {\n        public static final @NotNull StructCodec<SetFireworkExplosion> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetFireworkExplosion::predicates,\n                \"shape\", Codec.Enum(FireworkExplosion.Shape.class).optional(), SetFireworkExplosion::shape,\n                \"colors\", Color.CODEC.list().optional(), SetFireworkExplosion::colors,\n                \"fade_colors\", Color.CODEC.list().optional(), SetFireworkExplosion::fadeColors,\n                \"has_trail\", Codec.BOOLEAN.optional(), SetFireworkExplosion::trail,\n                \"has_twinkle\", Codec.BOOLEAN.optional(), SetFireworkExplosion::twinkle,\n                SetFireworkExplosion::new\n        );\n\n        private static final @NotNull FireworkExplosion DEFAULT = new FireworkExplosion(FireworkExplosion.Shape.SMALL_BALL, List.of(), List.of(), false, false);\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            FireworkExplosion firework = input.get(DataComponents.FIREWORK_EXPLOSION, DEFAULT);\n\n            FireworkExplosion updated = new FireworkExplosion(\n                    this.shape != null ? this.shape : firework.shape(),\n                    this.colors != null ? this.colors : firework.colors(),\n                    this.fadeColors != null ? this.fadeColors : firework.fadeColors(),\n                    this.trail != null ? this.trail : firework.hasTrail(),\n                    this.twinkle != null ? this.twinkle : firework.hasTwinkle()\n            );\n\n            return input.with(DataComponents.FIREWORK_EXPLOSION, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetFireworks(@NotNull List<LootPredicate> predicates, @Nullable Integer flightDuration, @NotNull ListOperation.WithValues<FireworkExplosion> explosions) implements LootFunction {\n        public static final @NotNull StructCodec<SetFireworks> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetFireworks::predicates,\n                \"flight_duration\", Codec.INT.optional(), SetFireworks::flightDuration,\n                \"explosions\", ListOperation.WithValues.codec(FireworkExplosion.CODEC), SetFireworks::explosions,\n                SetFireworks::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            FireworkList list = input.get(DataComponents.FIREWORKS, FireworkList.EMPTY);\n\n            FireworkList updated = new FireworkList(\n                    this.flightDuration != null ? this.flightDuration.byteValue() : list.flightDuration(),\n                    explosions.operation().apply(this.explosions.values(), list.explosions())\n            );\n\n            return input.with(DataComponents.FIREWORKS, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetInstrument(@NotNull List<LootPredicate> predicates, @NotNull RegistryTag<Instrument> options) implements LootFunction {\n        public static final @NotNull StructCodec<SetInstrument> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetInstrument::predicates,\n                \"options\", RegistryTag.codec(Registries::instrument), SetInstrument::options,\n                SetInstrument::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n            if (options.size() == 0) return input;\n\n            int count = context.require(LootContext.RANDOM).nextInt(options.size());\n\n            var it = options.iterator();\n            for (int i = 0; i < count; i++) {\n                it.next();\n            }\n\n            return input.with(DataComponents.INSTRUMENT, new InstrumentComponent(Either.right(it.next())));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetItem(@NotNull List<LootPredicate> predicates, @NotNull Material item) implements LootFunction {\n        public static final @NotNull StructCodec<SetItem> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetItem::predicates,\n                \"item\", Material.CODEC, SetItem::item,\n                SetItem::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            return input.builder().material(item).build();\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetLootTable(@NotNull List<LootPredicate> predicates, @NotNull Key name, long seed) implements LootFunction {\n        public static final @NotNull StructCodec<SetLootTable> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetLootTable::predicates,\n                \"name\", Codec.KEY, SetLootTable::name,\n                \"seed\", Codec.LONG.optional(0L), SetLootTable::seed,\n                SetLootTable::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n            if (input.isAir()) return input;\n\n            return input.with(DataComponents.CONTAINER_LOOT, new SeededContainerLoot(name.asString(), seed));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetLore(@NotNull List<LootPredicate> predicates, @NotNull List<Component> lore,\n                   @NotNull ListOperation operation, @Nullable RelevantEntity entity) implements LootFunction {\n        public static final @NotNull StructCodec<SetLore> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetLore::predicates,\n                \"lore\", Codec.COMPONENT.list(), SetLore::lore,\n                StructCodec.INLINE, ListOperation.CODEC, SetLore::operation,\n                \"entity\", RelevantEntity.CODEC.optional(), SetLore::entity,\n                SetLore::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            List<Component> components = input.get(DataComponents.LORE, List.of());\n\n            // TODO: Incomplete\n            // TODO: https://minecraft.wiki/w/Raw_JSON_text_format#Component_resolution\n            //       This is not used in vanilla so it's fine for now.\n\n            return input.with(DataComponents.LORE, operation.apply(lore, components));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetName(@NotNull List<LootPredicate> predicates, @Nullable Component name,\n                   @Nullable RelevantEntity entity, @NotNull Target target) implements LootFunction {\n        public static final @NotNull StructCodec<SetName> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetName::predicates,\n                \"name\", Codec.COMPONENT.optional(), SetName::name,\n                \"entity\", RelevantEntity.CODEC.optional(), SetName::entity,\n                \"target\", Target.CODEC.optional(Target.CUSTOM_NAME), SetName::target,\n                SetName::new\n        );\n\n        public enum Target {\n            ITEM_NAME(\"item_name\", DataComponents.ITEM_NAME),\n            CUSTOM_NAME(\"custom_name\", DataComponents.CUSTOM_NAME);\n\n            public static final @NotNull Codec<Target> CODEC = Codec.Enum(Target.class); // Relies on the enum names themselves being accurate\n\n            private final String id;\n            private final DataComponent<Component> component;\n\n            Target(String id, DataComponent<Component> component) {\n                this.id = id;\n                this.component = component;\n            }\n\n            public String id() {\n                return id;\n            }\n\n            public DataComponent<Component> component() {\n                return component;\n            }\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (name == null) return input;\n\n            Component component = this.name;\n            // TODO: Incomplete\n            // TODO: https://minecraft.wiki/w/Raw_JSON_text_format#Component_resolution\n            //       This is not used in vanilla so it's fine for now.\n\n            return input.with(target.component(), component);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetOminousBottleAmplifier(@NotNull List<LootPredicate> predicates, @NotNull LootNumber amplifier) implements LootFunction {\n        public static final @NotNull StructCodec<SetOminousBottleAmplifier> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetOminousBottleAmplifier::predicates,\n                \"amplifier\", LootNumber.CODEC, SetOminousBottleAmplifier::amplifier,\n                SetOminousBottleAmplifier::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            int amplifier = Math.max(0, Math.min(this.amplifier.getInt(context), 4));\n\n            return input.with(DataComponents.OMINOUS_BOTTLE_AMPLIFIER, amplifier);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetPotion(@NotNull List<LootPredicate> predicates, @NotNull Key id) implements LootFunction {\n        public static final @NotNull StructCodec<SetPotion> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetPotion::predicates,\n                \"id\", Codec.KEY, SetPotion::id,\n                SetPotion::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (id.asString().equals(\"minecraft:empty\")) {\n                return input.without(DataComponents.POTION_CONTENTS);\n            }\n\n            PotionContents existing = input.get(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);\n            PotionContents updated = new PotionContents(PotionType.fromKey(id), existing.customColor(), existing.customEffects());\n\n            return input.with(DataComponents.POTION_CONTENTS, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetStewEffect(@NotNull List<LootPredicate> predicates, @NotNull List<AddedEffect> effects) implements LootFunction {\n        public static final @NotNull StructCodec<SetStewEffect> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetStewEffect::predicates,\n                \"effects\", AddedEffect.CODEC.list(), SetStewEffect::effects,\n                SetStewEffect::new\n        );\n\n        public record AddedEffect(@NotNull PotionEffect effect, @NotNull LootNumber duration) {\n            public static final @NotNull StructCodec<AddedEffect> CODEC = StructCodec.struct(\n                    \"type\", PotionEffect.CODEC, AddedEffect::effect,\n                    \"duration\", LootNumber.CODEC, AddedEffect::duration,\n                    AddedEffect::new\n            );\n        }\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            if (!Material.SUSPICIOUS_STEW.equals(input.material()) || effects.isEmpty()) return input;\n\n            AddedEffect effect = effects.get(context.require(LootContext.RANDOM).nextInt(effects.size()));\n\n            long duration = effect.duration().getInt(context);\n            if (!effect.effect().registry().isInstantaneous()) {\n                duration *= ServerFlag.SERVER_TICKS_PER_SECOND;\n            }\n\n            SuspiciousStewEffects.Effect added = new SuspiciousStewEffects.Effect(effect.effect(), (int) duration);\n\n            SuspiciousStewEffects current = input.get(DataComponents.SUSPICIOUS_STEW_EFFECTS, SuspiciousStewEffects.EMPTY);\n            return input.with(DataComponents.SUSPICIOUS_STEW_EFFECTS, current.with(added));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetWritableBookPages(@NotNull List<LootPredicate> predicates, @NotNull List<FilteredText<String>> pages, @NotNull ListOperation operation) implements LootFunction {\n        public static final @NotNull StructCodec<SetWritableBookPages> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetWritableBookPages::predicates,\n                \"pages\", FilteredText.STRING_CODEC.list(), SetWritableBookPages::pages,\n                StructCodec.INLINE, ListOperation.CODEC, SetWritableBookPages::operation,\n                SetWritableBookPages::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            WritableBookContent content = input.get(DataComponents.WRITABLE_BOOK_CONTENT, WritableBookContent.EMPTY);\n\n            return input.with(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(operation.apply(pages, content.pages())));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n    record SetWrittenBookPages(@NotNull List<LootPredicate> predicates, @NotNull List<FilteredText<Component>> pages, @NotNull ListOperation operation) implements LootFunction {\n        public static final @NotNull StructCodec<SetWrittenBookPages> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), SetWrittenBookPages::predicates,\n                \"pages\", FilteredText.COMPONENT_CODEC.list(), SetWrittenBookPages::pages,\n                StructCodec.INLINE, ListOperation.CODEC, SetWrittenBookPages::operation,\n                SetWrittenBookPages::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            WrittenBookContent content = input.get(DataComponents.WRITTEN_BOOK_CONTENT, WrittenBookContent.EMPTY);\n            WrittenBookContent updated = new WrittenBookContent(content.title(), content.author(), content.generation(), operation.apply(pages, content.pages()), content.resolved());\n\n            return input.with(DataComponents.WRITTEN_BOOK_CONTENT, updated);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record Sequence(@NotNull List<LootFunction> functions) implements LootFunction {\n        public static final @NotNull StructCodec<Sequence> CODEC = StructCodec.struct(\n                \"functions\", LootFunction.CODEC.list(), Sequence::functions,\n                Sequence::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            return LootFunction.apply(functions, input, context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n    \n    record ToggleTooltips(@NotNull List<LootPredicate> predicates, @NotNull Map<DataComponent<?>, Boolean> toggles) implements LootFunction {\n        public static final @NotNull StructCodec<ToggleTooltips> CODEC = StructCodec.struct(\n                \"conditions\", LootPredicate.CODEC.list().optional(List.of()), ToggleTooltips::predicates,\n                \"toggles\", DataComponent.CODEC.mapValue(StructCodec.BOOLEAN), ToggleTooltips::toggles,\n                ToggleTooltips::new\n        );\n\n        @Override\n        public @NotNull ItemStack apply(@NotNull ItemStack input, @NotNull LootContext context) {\n            if (!LootPredicate.all(predicates, context)) return input;\n\n            TooltipDisplay display = input.get(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.EMPTY);\n\n            for (var entry : toggles.entrySet()) {\n                display = entry.getValue() ? display.with(entry.getKey()) : display.without(entry.getKey());\n            }\n\n            return input.with(DataComponents.TOOLTIP_DISPLAY, display);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootFunction> codec() {\n            return CODEC;\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootGenerator.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\n\n/**\n * Something that can generate loot.\n */\npublic interface LootGenerator {\n\n    @NotNull List<ItemStack> generate(@NotNull LootContext context);\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootNBT.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.BinaryTag;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.IntBinaryTag;\nimport net.kyori.adventure.nbt.StringBinaryTag;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.vanilla.loot.util.RelevantEntity;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\n\n/**\n * Returns NBT data from the provided context.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootNBT {\n\n    @NotNull Codec<LootNBT> CODEC = Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends LootNBT>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, LootNBT::codec, \"type\").orElse(Codec.STRING.transform(\n            str -> new Context(Context.Target.fromString(str)),\n            nbt -> ((Context) nbt).target.toString()\n    ));\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootNBT>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootNBT>> registry = DynamicRegistry.create(Key.key(\"loot_nbt\"));\n        registry.register(\"context\", Context.CODEC);\n        registry.register(\"storage\", Storage.CODEC);\n        return registry;\n    }\n\n    /**\n     * Generates some NBT based on the provided context.\n     * @param context the context to use for NBT\n     * @return the NBT, or null if there is none\n     */\n    @Nullable BinaryTag getNBT(@NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this function\n     */\n    @NotNull StructCodec<? extends LootNBT> codec();\n\n    record Storage(@NotNull Key source) implements LootNBT {\n        public static final @NotNull StructCodec<Storage> CODEC = StructCodec.struct(\n                \"source\", Codec.KEY, Storage::source,\n                Storage::new\n        );\n\n        @Override\n        public @Nullable BinaryTag getNBT(@NotNull LootContext context) {\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement command storage (Key -> CompoundBinaryTag)\");\n\n            return null;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNBT> codec() {\n            return CODEC;\n        }\n    }\n\n    record Context(@NotNull Target target) implements LootNBT {\n        public static final @NotNull StructCodec<Context> CODEC = StructCodec.struct(\n                \"target\", Codec.STRING.transform(Context.Target::fromString, Context.Target::toString), Context::target,\n                Context::new\n        );\n\n        public sealed interface Target {\n            @Nullable BinaryTag getNBT(@NotNull LootContext context);\n\n            static @NotNull Target fromString(@NotNull String input) {\n                if (input.equals(\"block_entity\")) return new BlockEntity();\n\n                for (var target : RelevantEntity.values()) {\n                    if (target.id().equals(input)) return new Entity(target);\n                }\n\n                throw new IllegalArgumentException(\"Expected block_entity or a valid entity target name\");\n            }\n\n            record BlockEntity() implements Target {\n                @SuppressWarnings(\"DataFlowIssue\")\n                @Override\n                public @NotNull BinaryTag getNBT(@NotNull LootContext context) {\n                    Block block = context.require(LootContext.BLOCK_STATE);\n                    Point pos = context.require(LootContext.ORIGIN);\n\n                    CompoundBinaryTag nbt = block.hasNbt() ? block.nbt() : CompoundBinaryTag.empty();\n\n                    return nbt.put(Map.of(\n                            \"x\", IntBinaryTag.intBinaryTag(pos.blockX()),\n                            \"y\", IntBinaryTag.intBinaryTag(pos.blockY()),\n                            \"z\", IntBinaryTag.intBinaryTag(pos.blockZ()),\n                            \"id\", StringBinaryTag.stringBinaryTag(block.key().asString())\n                    ));\n                }\n\n                @Override\n                public String toString() {\n                    return \"block_entity\";\n                }\n            }\n\n            record Entity(@NotNull RelevantEntity target) implements Target {\n                @Override\n                public @NotNull BinaryTag getNBT(@NotNull LootContext context) {\n                    var entity = context.require(target.key());\n\n                    if (true) throw new UnsupportedOperationException(\"TODO: Implement entity serialization (Entity entity -> BinaryTag)\");\n                    return null;\n                }\n\n                @Override\n                public String toString() {\n                    return target.id();\n                }\n            }\n        }\n\n        @Override\n        public @Nullable BinaryTag getNBT(@NotNull LootContext context) {\n            return target.getNBT(context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNBT> codec() {\n            return CODEC;\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootNumber.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.IntBinaryTag;\nimport net.kyori.adventure.nbt.NumberBinaryTag;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.item.enchant.LevelBasedValue;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.vanilla.loot.util.nbt.NBTPath;\nimport net.minestom.vanilla.loot.util.nbt.NBTReference;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Generates numbers based on provided loot contexts.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootNumber {\n\n    @NotNull Codec<LootNumber> CODEC = Codec.DOUBLE.<LootNumber>transform(Constant::new, a -> ((Constant) a).value()).orElse(Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends LootNumber>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, LootNumber::codec, \"type\"));\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootNumber>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootNumber>> registry = DynamicRegistry.create(Key.key(\"loot_numbers\"));\n        registry.register(\"binomial\", Binomial.CODEC);\n        registry.register(\"constant\", Constant.CODEC);\n        registry.register(\"enchantment_level\", EnchantmentLevel.CODEC);\n        registry.register(\"score\", Score.CODEC);\n        registry.register(\"storage\", Storage.CODEC);\n        registry.register(\"uniform\", Uniform.CODEC);\n        return registry;\n    }\n\n    /**\n     * Generates an integer depending on the information in the provided context.<br>\n     * This is an explicitly impure method—it depends on state outside the given context.\n     * @param context the context object, to use if required\n     * @return the integer generated by this loot number for the provided context\n     */\n    int getInt(@NotNull LootContext context);\n\n    /**\n     * Generates a double depending on the information in the provided context.<br>\n     * This is an explicitly impure method—it depends on state outside the given context.\n     * @param context the context object, to use if required\n     * @return the double generated by this loot number for the provided context\n     */\n    double getDouble(@NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this number\n     */\n    @NotNull StructCodec<? extends LootNumber> codec();\n\n    record Binomial(@NotNull LootNumber trials, @NotNull LootNumber probability) implements LootNumber {\n        public static final @NotNull StructCodec<Binomial> CODEC = StructCodec.struct(\n                \"n\", LootNumber.CODEC, Binomial::trials,\n                \"p\", LootNumber.CODEC, Binomial::probability,\n                Binomial::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            int trials = trials().getInt(context);\n            double probability = probability().getDouble(context);\n            Random random = context.require(LootContext.RANDOM);\n\n            int successes = 0;\n            for (int trial = 0; trial < trials; trial++) {\n                if (random.nextDouble() < probability) {\n                    successes++;\n                }\n            }\n            return successes;\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            return getInt(context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n    record Constant(@NotNull Double value) implements LootNumber {\n        public static final @NotNull StructCodec<Constant> CODEC = StructCodec.struct(\n                \"value\", Codec.DOUBLE, Constant::value,\n                Constant::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            return value.intValue();\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            return value;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n    record EnchantmentLevel(@NotNull LevelBasedValue amount) implements LootNumber {\n        public static final @NotNull StructCodec<EnchantmentLevel> CODEC = StructCodec.struct(\n                \"amount\", LevelBasedValue.CODEC, EnchantmentLevel::amount,\n                EnchantmentLevel::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            return (int) Math.round(getDouble(context));\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            return amount.calc(context.require(LootContext.ENCHANTMENT_LEVEL));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n    record Score(@NotNull LootScore target, @NotNull String objective, double scale) implements LootNumber {\n        public static final @NotNull StructCodec<Score> CODEC = StructCodec.struct(\n                \"target\", LootScore.CODEC, Score::target,\n                \"score\", Codec.STRING, Score::objective,\n                \"scale\", Codec.DOUBLE, Score::scale,\n                Score::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            return (int) Math.round(getDouble(context));\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            var score = target.apply(context).apply(objective);\n\n            return score != null ? score * scale : 0;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n    record Storage(@NotNull Key storage, @NotNull NBTPath path) implements LootNumber {\n        public static final @NotNull StructCodec<Storage> CODEC = StructCodec.struct(\n                \"storage\", Codec.KEY, Storage::storage,\n                \"path\", NBTPath.CODEC, Storage::path,\n                Storage::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            return get(context).intValue();\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            return get(context).doubleValue();\n        }\n\n        private NumberBinaryTag get(@NotNull LootContext context) {\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement entity scores (Entity entity -> String objective -> @Nullable Integer)\");\n            CompoundBinaryTag compound = null;\n\n            List<NBTReference> refs = path.get(compound != null ? compound : CompoundBinaryTag.empty());\n            if (refs.size() != 1) return IntBinaryTag.intBinaryTag(0);\n\n            if (refs.getFirst().get() instanceof NumberBinaryTag number) {\n                return number;\n            } else {\n                return IntBinaryTag.intBinaryTag(0);\n            }\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n    record Uniform(@NotNull LootNumber min, @NotNull LootNumber max) implements LootNumber {\n        public static final @NotNull StructCodec<Uniform> CODEC = StructCodec.struct(\n                \"min\", LootNumber.CODEC, Uniform::min,\n                \"max\", LootNumber.CODEC, Uniform::max,\n                Uniform::new\n        );\n\n        @Override\n        public int getInt(@NotNull LootContext context) {\n            return context.require(LootContext.RANDOM).nextInt(min().getInt(context), max().getInt(context) + 1);\n        }\n\n        @Override\n        public double getDouble(@NotNull LootContext context) {\n            return context.require(LootContext.RANDOM).nextDouble(min().getDouble(context), max().getDouble(context));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootNumber> codec() {\n            return CODEC;\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootPool.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A loot pool.\n * @param rolls the default number of rolls to occur\n * @param bonusRolls the number of bonus rolls to occur. Multiplied by the context's luck.\n * @param entries the entries to generate loot from.\n * @param predicates the predicates for loot generation\n * @param functions the modifiers applied to each item\n */\npublic record LootPool(@NotNull LootNumber rolls,\n                       @NotNull LootNumber bonusRolls,\n                       @NotNull List<LootEntry> entries,\n                       @NotNull List<LootPredicate> predicates,\n                       @NotNull List<LootFunction> functions) implements LootGenerator {\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static final @NotNull StructCodec<LootPool> CODEC = StructCodec.struct(\n            \"rolls\", LootNumber.CODEC, LootPool::rolls,\n            \"bonus_rolls\", LootNumber.CODEC.optional(new LootNumber.Constant(0D)), LootPool::bonusRolls,\n            \"entries\", LootEntry.CODEC.list(), LootPool::entries,\n            \"conditions\", LootPredicate.CODEC.list().optional(List.of()), LootPool::predicates,\n            \"functions\", LootFunction.CODEC.list().optional(List.of()), LootPool::functions,\n            LootPool::new\n    );\n\n    @Override\n    public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n        if (!(LootPredicate.all(predicates, context))) return List.of();\n\n        int rolls = this.rolls.getInt(context);\n\n        Double luck = context.get(LootContext.LUCK);\n        if (luck != null) {\n            rolls += (int) Math.floor(luck * this.bonusRolls.getDouble(context));\n        }\n\n        List<ItemStack> items = new ArrayList<>();\n\n        for (int i = 0; i < rolls; i++) {\n            LootEntry.Choice choice = pickChoice(entries, context);\n            if (choice == null) continue;\n\n            items.addAll(choice.generate(context));\n        }\n\n        return LootFunction.apply(functions, items, context);\n    }\n\n    /**\n     * Picks a random choice from the choices generated by the provided entries, weighted with each choice's weight. If\n     * no choices were generated, null is returned.\n     * @param entries the entries to generate choices to choose from\n     * @param context the context, to use if needed\n     * @return the picked choice, or null if no choices were generated\n     */\n    static @Nullable LootEntry.Choice pickChoice(@NotNull List<LootEntry> entries, @NotNull LootContext context) {\n        List<LootEntry.Choice> choices = new ArrayList<>();\n        for (LootEntry entry : entries) {\n            choices.addAll(entry.requestChoices(context));\n        }\n\n        if (choices.isEmpty()) {\n            return null;\n        }\n\n        long totalWeight = 0;\n        long[] weightMilestones = new long[choices.size()];\n        for (int i = 0; i < choices.size(); i++) {\n            // Prevent the weight of this choice from being less than 1\n            totalWeight += Math.max(1, choices.get(i).getWeight(context));\n\n            weightMilestones[i] = totalWeight;\n        }\n\n        long value = context.require(LootContext.RANDOM).nextLong(0, totalWeight);\n\n        LootEntry.Choice choice = choices.getLast();\n\n        for (int i = 0; i < weightMilestones.length; i++) {\n            if (value < weightMilestones[i]) {\n                choice = choices.get(i);\n                break;\n            }\n        }\n\n        return choice;\n    }\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootPredicate.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.Weather;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.block.predicate.PropertiesPredicate;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.item.enchant.LevelBasedValue;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.server.registry.Registries;\nimport net.minestom.server.registry.RegistryKey;\nimport net.minestom.vanilla.loot.util.EnchantmentUtils;\nimport net.minestom.vanilla.loot.util.LootNumberRange;\nimport net.minestom.vanilla.loot.util.RelevantEntity;\nimport net.minestom.vanilla.loot.util.predicate.DamageSourcePredicate;\nimport net.minestom.vanilla.loot.util.predicate.EntityPredicate;\nimport net.minestom.vanilla.loot.util.predicate.ItemPredicate;\nimport net.minestom.vanilla.loot.util.predicate.LocationPredicate;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Predicate;\n\n/**\n * A predicate over a loot context, returning whether or not a given context passes some arbitrary predicate.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootPredicate extends Predicate<@NotNull LootContext> {\n\n    @NotNull StructCodec<LootPredicate> CODEC = Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends LootPredicate>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, LootPredicate::codec, \"condition\");\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootPredicate>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootPredicate>> registry = DynamicRegistry.create(Key.key(\"loot_predicates\"));\n        registry.register(\"all_of\", AllOf.CODEC);\n        registry.register(\"any_of\", AnyOf.CODEC);\n        registry.register(\"block_state_property\", BlockStateProperty.CODEC);\n        registry.register(\"damage_source_properties\", DamageSourceProperties.CODEC);\n        registry.register(\"enchantment_active_check\", EnchantmentActiveCheck.CODEC);\n        registry.register(\"entity_properties\", EntityProperties.CODEC);\n        registry.register(\"entity_scores\", EntityScores.CODEC);\n        registry.register(\"inverted\", Inverted.CODEC);\n        registry.register(\"killed_by_player\", KilledByPlayer.CODEC);\n        registry.register(\"location_check\", LocationCheck.CODEC);\n        registry.register(\"match_tool\", MatchTool.CODEC);\n        registry.register(\"random_chance\", RandomChance.CODEC);\n        registry.register(\"random_chance_with_enchanted_bonus\", RandomChanceWithEnchantedBonus.CODEC);\n        registry.register(\"reference\", Reference.CODEC);\n        registry.register(\"survives_explosion\", SurvivesExplosion.CODEC);\n        registry.register(\"table_bonus\", TableBonus.CODEC);\n        registry.register(\"time_check\", TimeCheck.CODEC);\n        registry.register(\"value_check\", ValueCheck.CODEC);\n        registry.register(\"weather_check\", WeatherCheck.CODEC);\n        return registry;\n    }\n\n    /**\n     * Returns whether or not the provided loot context passes this predicate.\n     * @param context the context object, to use if required\n     * @return true if the provided loot context is valid according to this predicate\n     */\n    @Override\n    boolean test(@NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this predicate\n     */\n    @NotNull StructCodec<? extends LootPredicate> codec();\n\n    /**\n     * Returns whether or not every given predicate verifies the provided context.\n     */\n    static boolean all(@NotNull List<LootPredicate> predicates, @NotNull LootContext context) {\n        if (predicates.isEmpty()) {\n            return true;\n        }\n        for (var predicate : predicates) {\n            if (!predicate.test(context)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    record AllOf(@NotNull List<LootPredicate> terms) implements LootPredicate {\n        public static final @NotNull StructCodec<AllOf> CODEC = StructCodec.struct(\n                \"terms\", LootPredicate.CODEC.list(), AllOf::terms,\n                AllOf::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return all(terms, context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record AnyOf(@NotNull List<LootPredicate> terms) implements LootPredicate {\n        public static final @NotNull StructCodec<AnyOf> CODEC = StructCodec.struct(\n                \"terms\", LootPredicate.CODEC.list(), AnyOf::terms,\n                AnyOf::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            if (terms.isEmpty()) {\n                return false;\n            }\n            for (var predicate : terms) {\n                if (predicate.test(context)) {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record BlockStateProperty(@NotNull Key block, @Nullable PropertiesPredicate properties) implements LootPredicate {\n        public static final @NotNull StructCodec<BlockStateProperty> CODEC = StructCodec.struct(\n                \"block\", Codec.KEY, BlockStateProperty::block,\n                \"properties\", PropertiesPredicate.CODEC.optional(), BlockStateProperty::properties,\n                BlockStateProperty::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Block block = context.get(LootContext.BLOCK_STATE);\n\n            return block != null && this.block.equals(block.key()) && (properties == null || properties.test(block));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record DamageSourceProperties(@Nullable DamageSourcePredicate predicate) implements LootPredicate {\n        public static final @NotNull StructCodec<DamageSourceProperties> CODEC = StructCodec.struct(\n                \"predicate\", DamageSourcePredicate.CODEC.optional(), DamageSourceProperties::predicate,\n                DamageSourceProperties::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Instance world = context.get(LootContext.WORLD);\n            Point origin = context.get(LootContext.ORIGIN);\n            DamageType damage = context.get(LootContext.DAMAGE_SOURCE);\n\n            if (predicate == null || world == null || origin == null || damage == null) {\n                return false;\n            }\n\n            return predicate.test(world, origin, damage);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record EnchantmentActiveCheck(boolean active) implements LootPredicate {\n        public static final @NotNull StructCodec<EnchantmentActiveCheck> CODEC = StructCodec.struct(\n                \"active\", Codec.BOOLEAN, EnchantmentActiveCheck::active,\n                EnchantmentActiveCheck::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return context.require(LootContext.ENCHANTMENT_ACTIVE) == active;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record EntityProperties(@Nullable EntityPredicate predicate, @NotNull RelevantEntity entity) implements LootPredicate {\n        public static final @NotNull StructCodec<EntityProperties> CODEC = StructCodec.struct(\n                \"predicate\", EntityPredicate.CODEC, EntityProperties::predicate,\n                \"entity\", RelevantEntity.CODEC, EntityProperties::entity,\n                EntityProperties::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Entity entity = context.get(this.entity.key());\n            Point origin = context.get(LootContext.ORIGIN);\n\n            return predicate == null || predicate.test(context.require(LootContext.WORLD), origin, entity);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record EntityScores(@NotNull Map<String, LootNumberRange> scores, @NotNull RelevantEntity entity) implements LootPredicate {\n        public static final @NotNull StructCodec<EntityScores> CODEC = StructCodec.struct(\n                \"scores\", Codec.STRING.mapValue(LootNumberRange.CODEC), EntityScores::scores,\n                \"entity\", RelevantEntity.CODEC, EntityScores::entity,\n                EntityScores::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Entity entity = context.get(this.entity.key());\n            if (entity == null) return false;\n\n            for (var entry : scores.entrySet()) {\n                if (true) throw new UnsupportedOperationException(\"TODO: Implement entity scores (Entity entity -> String objective -> @Nullable Integer)\");\n                Integer score = null;\n                if (score == null || !entry.getValue().check(context, score)) {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record Inverted(@NotNull LootPredicate term) implements LootPredicate {\n        public static final @NotNull StructCodec<Inverted> CODEC = StructCodec.struct(\n                \"term\", LootPredicate.CODEC, Inverted::term,\n                Inverted::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return !term.test(context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record KilledByPlayer() implements LootPredicate {\n        public static final @NotNull StructCodec<KilledByPlayer> CODEC = StructCodec.struct(KilledByPlayer::new);\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return context.has(LootContext.LAST_DAMAGE_PLAYER);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record LocationCheck(@Nullable LocationPredicate predicate, double offsetX, double offsetY, double offsetZ) implements LootPredicate {\n        public static final @NotNull StructCodec<LocationCheck> CODEC = StructCodec.struct(\n                \"predicate\", LocationPredicate.CODEC, LocationCheck::predicate,\n                \"offsetX\", Codec.DOUBLE.optional(0D), LocationCheck::offsetX,\n                \"offsetY\", Codec.DOUBLE.optional(0D), LocationCheck::offsetY,\n                \"offsetZ\", Codec.DOUBLE.optional(0D), LocationCheck::offsetZ,\n                LocationCheck::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Point origin = context.get(LootContext.ORIGIN);\n\n            if (origin == null) return false;\n            if (predicate == null) return true;\n\n            return predicate.test(context.require(LootContext.WORLD), origin.add(offsetX, offsetY, offsetZ));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record MatchTool(@Nullable ItemPredicate predicate) implements LootPredicate {\n        public static final @NotNull StructCodec<MatchTool> CODEC = StructCodec.struct(\n                \"predicate\", ItemPredicate.CODEC, MatchTool::predicate,\n                MatchTool::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            ItemStack tool = context.get(LootContext.TOOL);\n\n            if (tool == null) return false;\n            if (predicate == null) return true;\n\n            return predicate.test(tool, context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record RandomChance(@NotNull LootNumber chance) implements LootPredicate {\n        public static final @NotNull StructCodec<RandomChance> CODEC = StructCodec.struct(\n                \"chance\", LootNumber.CODEC, RandomChance::chance,\n                RandomChance::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return context.require(LootContext.RANDOM).nextDouble() < chance.getDouble(context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record RandomChanceWithEnchantedBonus(@NotNull RegistryKey<Enchantment> enchantment, float unenchantedChance, @NotNull LevelBasedValue enchantedChance) implements LootPredicate {\n        public static final @NotNull StructCodec<RandomChanceWithEnchantedBonus> CODEC = StructCodec.struct(\n                \"enchantment\", RegistryKey.codec(Registries::enchantment), RandomChanceWithEnchantedBonus::enchantment,\n                \"unenchanted_chance\", Codec.FLOAT, RandomChanceWithEnchantedBonus::unenchantedChance,\n                \"enchanted_chance\", LevelBasedValue.CODEC, RandomChanceWithEnchantedBonus::enchantedChance,\n                RandomChanceWithEnchantedBonus::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Entity attacker = context.get(LootContext.ATTACKING_ENTITY);\n\n            int level = EnchantmentUtils.level(attacker, enchantment);\n\n            float chance = level > 0 ? enchantedChance.calc(level) : unenchantedChance;\n            return context.require(LootContext.RANDOM).nextFloat() < chance;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record Reference(@NotNull Key name) implements LootPredicate {\n        public static final @NotNull StructCodec<Reference> CODEC = StructCodec.struct(\n                \"name\", Codec.KEY, Reference::name,\n                Reference::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            if (true) throw new UnsupportedOperationException(\"TODO: Implement loot predicate registry (Key -> @Nullable LootPredicate)\");\n            LootPredicate predicate = null;\n\n            return predicate != null && predicate.test(context);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record SurvivesExplosion() implements LootPredicate {\n        public static final @NotNull StructCodec<SurvivesExplosion> CODEC = StructCodec.struct(SurvivesExplosion::new);\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Float radius = context.get(LootContext.EXPLOSION_RADIUS);\n            return radius == null || context.require(LootContext.RANDOM).nextFloat() <= (1 / radius);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record TableBonus(@NotNull RegistryKey<Enchantment> enchantment, @NotNull List<Float> chances) implements LootPredicate {\n        public static final @NotNull StructCodec<TableBonus> CODEC = StructCodec.struct(\n                \"enchantment\", RegistryKey.codec(Registries::enchantment), TableBonus::enchantment,\n                \"chances\", Codec.FLOAT.list(), TableBonus::chances,\n                TableBonus::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            ItemStack tool = context.get(LootContext.TOOL);\n\n            int level = EnchantmentUtils.level(tool, enchantment);\n\n            float chance = chances.get(Math.min(this.chances.size() - 1, level));\n\n            return context.require(LootContext.RANDOM).nextFloat() < chance;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record TimeCheck(@Nullable Long period, @NotNull LootNumberRange value) implements LootPredicate {\n        public static final @NotNull StructCodec<TimeCheck> CODEC = StructCodec.struct(\n                \"period\", Codec.LONG.optional(), TimeCheck::period,\n                \"value\", LootNumberRange.CODEC, TimeCheck::value,\n                TimeCheck::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            long time = context.require(LootContext.WORLD).getTime();\n\n            if (period != null) {\n                time %= period;\n            }\n\n            return value.check(context, time);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record ValueCheck(@NotNull LootNumber value, @NotNull LootNumberRange range) implements LootPredicate {\n        public static final @NotNull StructCodec<ValueCheck> CODEC = StructCodec.struct(\n                \"value\", LootNumber.CODEC, ValueCheck::value,\n                \"range\", LootNumberRange.CODEC, ValueCheck::range,\n                ValueCheck::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            return range.check(context, value.getInt(context));\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n    record WeatherCheck(@Nullable Boolean raining, @Nullable Boolean thundering) implements LootPredicate {\n        public static final @NotNull StructCodec<WeatherCheck> CODEC = StructCodec.struct(\n                \"raining\", Codec.BOOLEAN.optional(), WeatherCheck::raining,\n                \"thundering\", Codec.BOOLEAN.optional(), WeatherCheck::thundering,\n                WeatherCheck::new\n        );\n\n        @Override\n        public boolean test(@NotNull LootContext context) {\n            Weather weather = context.require(LootContext.WORLD).getWeather();\n\n            return (raining == null || raining == weather.isRaining()) &&\n                    (thundering == null || thundering == weather.thunderLevel() > 0);\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootPredicate> codec() {\n            return CODEC;\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootScore.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.registry.DynamicRegistry;\nimport net.minestom.vanilla.loot.util.RelevantEntity;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.function.Function;\n\n/**\n * A score provider that produces functions that map an objective to a score value.\n */\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LootScore extends Function<@NotNull LootContext, Function<@NotNull String, @Nullable Integer>> {\n\n    @NotNull Codec<LootScore> CODEC = RelevantEntity.CODEC.<LootScore>transform(Context::new, c -> ((Context) c).name()).orElse(Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends LootScore>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, LootScore::codec, \"type\"));\n\n    static @NotNull DynamicRegistry<StructCodec<? extends LootScore>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends LootScore>> registry = DynamicRegistry.create(Key.key(\"loot_scores\"));\n        registry.register(\"context\", Context.CODEC);\n        registry.register(\"fixed\", Fixed.CODEC);\n        return registry;\n    }\n\n    @Override\n    @NotNull Function<@NotNull String, @Nullable Integer> apply(@NotNull LootContext context);\n\n    /**\n     * @return the codec that can encode this score\n     */\n    @NotNull StructCodec<? extends LootScore> codec();\n\n    record Context(@NotNull RelevantEntity name) implements LootScore {\n        public static final @NotNull StructCodec<Context> CODEC = StructCodec.struct(\n                \"name\", RelevantEntity.CODEC, Context::name,\n                Context::new\n        );\n\n        @Override\n        public @NotNull Function<@NotNull String, @Nullable Integer> apply(@NotNull LootContext context) {\n            throw new UnsupportedOperationException(\"TODO: Implement entity scores (Entity entity -> String objective -> @Nullable Integer)\");\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootScore> codec() {\n            return CODEC;\n        }\n    }\n\n    record Fixed(@NotNull String name) implements LootScore {\n        public static final @NotNull StructCodec<Fixed> CODEC = StructCodec.struct(\n                \"name\", Codec.STRING, Fixed::name,\n                Fixed::new\n        );\n\n        @Override\n        public @NotNull Function<@NotNull String, @Nullable Integer> apply(@NotNull LootContext context) {\n            throw new UnsupportedOperationException(\"TODO: Implement entity scores (String name -> String objective -> @Nullable Integer)\");\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends LootScore> codec() {\n            return CODEC;\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/LootTable.java",
    "content": "package net.minestom.vanilla.loot;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.item.ItemStack;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A loot table.\n * @param pools the pools that generate items in this table\n * @param functions the functions applied to each output item of this table\n * @param randomSequence An ID specifying the name of the random sequence that is used to generate loot from this loot table.\n */\npublic record LootTable(@NotNull List<LootPool> pools, @NotNull List<LootFunction> functions, @Nullable Key randomSequence) implements LootGenerator {\n\n    public static final @NotNull LootTable EMPTY = new LootTable(List.of(), List.of(), null);\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static final @NotNull StructCodec<LootTable> CODEC = StructCodec.struct(\n            \"pools\", LootPool.CODEC.list().optional(List.of()), LootTable::pools,\n            \"functions\", LootFunction.CODEC.list().optional(List.of()), LootTable::functions,\n            \"random_sequence\", Codec.KEY.optional(), LootTable::randomSequence,\n            LootTable::new\n    );\n\n    @Override\n    public @NotNull List<ItemStack> generate(@NotNull LootContext context) {\n        List<ItemStack> items = new ArrayList<>();\n\n        for (var pool : pools) {\n            for (var item : pool.generate(context)) {\n                items.add(LootFunction.apply(functions, item, context));\n            }\n        }\n\n        return items;\n    }\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/EnchantmentUtils.java",
    "content": "package net.minestom.vanilla.loot.util;\n\nimport net.minestom.server.component.DataComponent;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EquipmentSlot;\nimport net.minestom.server.entity.LivingEntity;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.registry.RegistryKey;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class EnchantmentUtils {\n\n    private EnchantmentUtils() {\n    }\n\n    public static int level(@Nullable ItemStack item, @NotNull RegistryKey<Enchantment> key) {\n        if (item == null) return 0;\n\n        EnchantmentList enchantments = item.get(DataComponents.ENCHANTMENTS);\n        if (enchantments == null) return 0;\n\n        return enchantments.enchantments().getOrDefault(key, 0);\n    }\n\n    public static int level(@Nullable Entity entity, @NotNull RegistryKey<Enchantment> key) {\n        int level = 0;\n        if (entity instanceof LivingEntity living) {\n            for (EquipmentSlot slot : EquipmentSlot.values()) {\n                EnchantmentList ench = living.getEquipment(slot).get(DataComponents.ENCHANTMENTS);\n                if (ench == null) continue;\n\n                level = Math.max(level, ench.level(key));\n            }\n        }\n\n        return level;\n    }\n\n    public static @NotNull ItemStack modifyItem(@NotNull ItemStack item, @NotNull Consumer<Map<RegistryKey<Enchantment>, Integer>> enchantments) {\n        DataComponent<EnchantmentList> type = item.material().equals(Material.ENCHANTED_BOOK) ? DataComponents.STORED_ENCHANTMENTS : DataComponents.ENCHANTMENTS;\n\n        EnchantmentList component = item.get(type, EnchantmentList.EMPTY);\n\n        var map = new HashMap<>(component.enchantments());\n        enchantments.accept(map);\n\n        // Make the book enchanted!\n        if (!map.isEmpty() && item.material().equals(Material.BOOK)) {\n            return item.builder()\n                    .material(Material.ENCHANTED_BOOK)\n                    .set(DataComponents.STORED_ENCHANTMENTS, new EnchantmentList(map))\n                    .build();\n        } else {\n            return item.with(type, new EnchantmentList(map));\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/ListOperation.java",
    "content": "package net.minestom.vanilla.loot.util;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.registry.DynamicRegistry;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic sealed interface ListOperation {\n\n    @NotNull StructCodec<ListOperation> CODEC = Codec.RegistryTaggedUnion(registries -> {\n        class Holder {\n            static final @NotNull DynamicRegistry<StructCodec<? extends ListOperation>> CODEC = createDefaultRegistry();\n        }\n        return Holder.CODEC;\n    }, ListOperation::codec, \"type\");\n\n    static @NotNull DynamicRegistry<StructCodec<? extends ListOperation>> createDefaultRegistry() {\n        final DynamicRegistry<StructCodec<? extends ListOperation>> registry = DynamicRegistry.create(Key.key(\"list_operations\"));\n        registry.register(\"append\", Append.CODEC);\n        registry.register(\"insert\", Insert.CODEC);\n        registry.register(\"replace_all\", ReplaceAll.CODEC);\n        registry.register(\"replace_section\", ReplaceSection.CODEC);\n        return registry;\n    }\n\n    <T> @NotNull List<T> apply(@NotNull List<T> values, @NotNull List<T> input);\n\n    /**\n     * @return the codec that can encode this list operation\n     */\n    @NotNull StructCodec<? extends ListOperation> codec();\n\n    record WithValues<T>(@NotNull ListOperation operation, @NotNull List<T> values) {\n        public static <T> Codec<WithValues<T>> codec(Codec<T> codec) {\n            return StructCodec.struct(\n                    StructCodec.INLINE, ListOperation.CODEC, WithValues::operation,\n                    \"values\", codec.list(), WithValues::values,\n                    WithValues::new\n            );\n        }\n    }\n\n    record Append() implements ListOperation {\n        public static final @NotNull StructCodec<Append> CODEC = StructCodec.struct(Append::new);\n\n        @Override\n        public @NotNull <T> List<T> apply(@NotNull List<T> values, @NotNull List<T> input) {\n            return Stream.concat(input.stream(), values.stream()).toList();\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends ListOperation> codec() {\n            return CODEC;\n        }\n    }\n\n    record Insert(int offset) implements ListOperation {\n        public static final @NotNull StructCodec<Insert> CODEC = StructCodec.struct(\n                \"offset\", Codec.INT.optional(0), Insert::offset,\n                Insert::new\n        );\n\n        @Override\n        public @NotNull <T> List<T> apply(@NotNull List<T> values, @NotNull List<T> input) {\n            List<T> items = new ArrayList<>();\n            items.addAll(input.subList(0, this.offset));\n            items.addAll(values);\n            items.addAll(input.subList(this.offset, input.size()));\n            return items;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends ListOperation> codec() {\n            return CODEC;\n        }\n    }\n\n    record ReplaceAll() implements ListOperation {\n        public static final @NotNull StructCodec<ReplaceAll> CODEC = StructCodec.struct(ReplaceAll::new);\n\n        @Override\n        public @NotNull <T> List<T> apply(@NotNull List<T> values, @NotNull List<T> input) {\n            return values;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends ListOperation> codec() {\n            return CODEC;\n        }\n    }\n\n    record ReplaceSection(int offset, @Nullable Integer size) implements ListOperation {\n        public static final @NotNull StructCodec<ReplaceSection> CODEC = StructCodec.struct(\n                \"offset\", Codec.INT.optional(0), ReplaceSection::offset,\n                \"size\", Codec.INT.optional(), ReplaceSection::size,\n                ReplaceSection::new\n        );\n\n        @Override\n        public @NotNull <T> List<T> apply(@NotNull List<T> values, @NotNull List<T> input) {\n            List<T> items = new ArrayList<>();\n            items.addAll(input.subList(0, offset));\n            items.addAll(values);\n\n            int size = this.size != null ? this.size : values.size();\n\n            // Add truncated part of list of possible\n            if (offset + size < input.size()) {\n                items.addAll(input.subList(offset + size, input.size()));\n            }\n\n            return items;\n        }\n\n        @Override\n        public @NotNull StructCodec<? extends ListOperation> codec() {\n            return CODEC;\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/LootNumberRange.java",
    "content": "package net.minestom.vanilla.loot.util;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.vanilla.loot.LootContext;\nimport net.minestom.vanilla.loot.LootNumber;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * An inclusive number range based on loot numbers.\n * @param min the optional minimum value\n * @param max the optional maximum value\n */\npublic record LootNumberRange(@Nullable LootNumber min, @Nullable LootNumber max) {\n\n    @SuppressWarnings({\"UnstableApiUsage\", \"NumberEquality\"})\n    public static final @NotNull Codec<LootNumberRange> CODEC = Codec.DOUBLE.transform(LootNumber.Constant::new, LootNumber.Constant::value)\n            .transform(n -> new LootNumberRange(n, n), r -> {\n                if (r.min() instanceof LootNumber.Constant(Double min) && r.max() instanceof LootNumber.Constant(Double max) && min == max) {\n                    return (LootNumber.Constant) r.min();\n                } else throw new UnsupportedOperationException(\"Using struct codec\");\n            }).orElse(StructCodec.struct(\n                    \"min\", LootNumber.CODEC.optional(), LootNumberRange::min,\n                    \"max\", LootNumber.CODEC.optional(), LootNumberRange::max,\n                    LootNumberRange::new\n            ));\n\n    /**\n     * Limits the provided value to between the minimum and maximum.<br>\n     * This API currently guarantees that, if the minimum ends up being larger than the maximum, the resulting value\n     * will be equal to the maximum.\n     * @param context the context, to use for getting the values of the min and max\n     * @param number the number to constrain to between the minimum and maximum\n     * @return the constrained number\n     */\n    public long limit(@NotNull LootContext context, long number) {\n        if (this.min != null) {\n            number = Math.max(this.min.getInt(context), number);\n        }\n        if (this.max != null) {\n            number = Math.min(this.max.getInt(context), number);\n        }\n        return number;\n    }\n\n    /**\n     * Limits the provided value to between the minimum and maximum.<br>\n     * This API currently guarantees that, if the minimum ends up being larger than the maximum, the resulting value\n     * will be equal to the maximum.\n     * @param context the context, to use for getting the values of the min and max\n     * @param number the number to constrain to between the minimum and maximum\n     * @return the constrained number\n     */\n    public double limit(@NotNull LootContext context, double number) {\n        if (this.min != null) {\n            number = Math.max(this.min.getDouble(context), number);\n        }\n        if (this.max != null) {\n            number = Math.min(this.max.getDouble(context), number);\n        }\n        return number;\n    }\n\n    /**\n     * Assures that the provided number is not smaller than the minimum and is not larger than the maximum. If either of\n     * the bounds is null, it's always considered as passing.\n     * @param context the context, to use for getting the values of the min and max\n     * @param number the number to check the validity of\n     * @return true if the provided number fits within {@link #min()} and {@link #max()}, and false otherwise\n     */\n    public boolean check(@NotNull LootContext context, long number) {\n        return (this.min == null || this.min.getInt(context) <= number) &&\n                (this.max == null || this.max.getInt(context) >= number);\n    }\n\n    /**\n     * Assures that the provided number is not smaller than the minimum and is not larger than the maximum. If either of\n     * the bounds is null, it's always considered as passing.\n     * @param context the context, to use for getting the values of the min and max\n     * @param number the number to check the validity of\n     * @return true if the provided number fits within {@link #min()} and {@link #max()}, and false otherwise\n     */\n    public boolean check(@NotNull LootContext context, double number) {\n        return (this.min == null || this.min.getDouble(context) <= number) &&\n                (this.max == null || this.max.getDouble(context) >= number);\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/RelevantEntity.java",
    "content": "package net.minestom.vanilla.loot.util;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.vanilla.loot.LootContext;\nimport org.jetbrains.annotations.NotNull;\n\npublic enum RelevantEntity {\n    THIS(\"this\", LootContext.THIS_ENTITY),\n    ATTACKER(\"attacker\", LootContext.ATTACKING_ENTITY),\n    DIRECT_ATTACKER(\"direct_attacker\", LootContext.DIRECT_ATTACKING_ENTITY),\n    LAST_PLAYER_DAMAGE(\"killer_player\", LootContext.LAST_DAMAGE_PLAYER);\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static final @NotNull Codec<RelevantEntity> CODEC = Codec.Enum(RelevantEntity.class); // Relies on the enum names themselves being accurate\n\n    private final @NotNull String id;\n    private final @NotNull LootContext.Key<? extends Entity> key;\n\n    RelevantEntity(@NotNull String id, @NotNull LootContext.Key<? extends Entity> key) {\n        this.id = id;\n        this.key = key;\n    }\n\n    public @NotNull String id() {\n        return id;\n    }\n\n    public @NotNull LootContext.Key<? extends Entity> key() {\n        return key;\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/RelevantTarget.java",
    "content": "package net.minestom.vanilla.loot.util;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.vanilla.loot.LootContext;\nimport org.jetbrains.annotations.NotNull;\n\npublic enum RelevantTarget {\n    THIS(\"this\", LootContext.THIS_ENTITY),\n    ATTACKING_ENTITY(\"attacking_entity\", LootContext.ATTACKING_ENTITY),\n    LAST_DAMAGE_PLAYER(\"last_damage_player\", LootContext.LAST_DAMAGE_PLAYER),\n    BLOCK_ENTITY(\"block_entity\", LootContext.BLOCK_STATE);\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static final Codec<RelevantTarget> CODEC = Codec.Enum(RelevantTarget.class); // Relies on the enum names themselves being accurate\n\n    private final @NotNull String id;\n    private final @NotNull LootContext.Key<?> key;\n\n    RelevantTarget(@NotNull String id, @NotNull LootContext.Key<?> key) {\n        this.id = id;\n        this.key = key;\n    }\n\n    public @NotNull String id() {\n        return id;\n    }\n\n    public @NotNull LootContext.Key<?> key() {\n        return key;\n    }\n}"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/nbt/NBTPath.java",
    "content": "package net.minestom.vanilla.loot.util.nbt;\n\nimport it.unimi.dsi.fastutil.ints.IntSet;\nimport net.kyori.adventure.nbt.*;\nimport net.minestom.server.adventure.MinestomAdventure;\nimport net.minestom.server.codec.Codec;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\n/**\n * A NBTPath allows selecting specific elements from a NBT tree, based on a list of selectors. Each selector has the\n * ability to select any number of elements from its predecessor—this allows arbitrary item selection from any NBT type.\n * <br>\n * It also provides multiple ways to manipulate NBT results as there is no deeply mutable NBT implementation.\n */\npublic record NBTPath(@NotNull List<Selector> selectors) {\n\n    @SuppressWarnings(\"UnstableApiUsage\")\n    public static final @NotNull Codec<NBTPath> CODEC = Parser.CODEC;\n\n    /**\n     * Selects an arbitrary number of elements from provided NBT.\n     */\n    public sealed interface Selector {\n\n        /**\n         * Passes each selected NBT element into the given consumer.\n         */\n        void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer);\n\n        /**\n         * Modifies the provided {@code source} so that, if possible, this path selector will select at least one NBT\n         * element from it. If a placeholder element is needed, {@code nextElement} is to be used.\n         */\n        void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement);\n\n        /**\n         * Provides a tag that this selector may be willing to modify.\n         */\n        @NotNull BinaryTag preparedNBT();\n\n        record RootKey(@NotNull String key) implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                if (source.has(key)) {\n                    consumer.accept(source.get(key));\n                }\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {}\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return CompoundBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                return key;\n            }\n        }\n\n        record Key(@NotNull String key) implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                if (source.has(key)) {\n                    consumer.accept(source.get(key));\n                }\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {\n                if (!source.has(key)) {\n                    source.get(key).set(nextElement.get());\n                }\n            }\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return CompoundBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                return \".\" + key;\n            }\n        }\n\n        record CompoundFilter(@NotNull CompoundBinaryTag filter) implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                if (NBTUtils.compareNBT(filter, source.get(), false)) {\n                    consumer.accept(source);\n                }\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {\n                if (!NBTUtils.compareNBT(filter, source.get(), false)) {\n                    source.set(filter);\n                }\n            }\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return CompoundBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                try {\n                    return TagStringIO.get().asString(filter);\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n\n        record EntireList() implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                int size = source.listSize();\n                for (int i = 0; i < size; i++) {\n                    consumer.accept(source.get(i));\n                }\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {\n                if (source.listSize() == 0) {\n                    source.listAdd(nextElement.get());\n                }\n            }\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return ListBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                return \"[]\";\n            }\n        }\n\n        record Index(int index) implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                var newIndex = index >= 0 ? index : source.listSize() + index;\n\n                if (newIndex < 0 || newIndex >= source.listSize()) return;\n\n                consumer.accept(source.get(newIndex));\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {}\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return ListBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                return \"[\" + index + \"]\";\n            }\n        }\n\n        record ListFilter(@NotNull CompoundBinaryTag filter) implements Selector {\n            @Override\n            public void get(@NotNull NBTReference source, @NotNull Consumer<NBTReference> consumer) {\n                int listSize = source.listSize();\n                for (int i = 0; i < listSize; i++) {\n                    NBTReference ref = source.get(i);\n                    if (NBTUtils.compareNBT(filter, ref.get(), false)) {\n                        consumer.accept(ref);\n                    }\n                }\n            }\n\n            @Override\n            public void prepare(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> nextElement) {\n                int listSize = source.listSize();\n                if (listSize == -1) return;\n\n                for (int i = 0; i < listSize; i++) {\n                    if (NBTUtils.compareNBT(filter, source.get(i).get(), false)) {\n                        return;\n                    }\n                }\n\n                source.listAdd(filter);\n            }\n\n            @Override\n            public @NotNull BinaryTag preparedNBT() {\n                return ListBinaryTag.empty();\n            }\n\n            @Override\n            public String toString() {\n                try {\n                    return \"[\" + TagStringIO.get().asString(filter) + \"]\";\n                } catch (IOException e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n\n    }\n\n    /**\n     * Strings {@code source} through each selector in {@link #selectors()}, returning the selected results. It is\n     * possible for there to be none. Modifying the resulting NBT references does nothing.\n     * @param source the source, to get the NBT from\n     * @return the list of selected NBT, which may be empty\n     */\n    public @NotNull List<NBTReference> get(@NotNull BinaryTag source) {\n        List<NBTReference> references = List.of(NBTReference.of(source));\n        for (var selector : selectors()) {\n            List<NBTReference> newReferences = new ArrayList<>();\n\n            references.forEach(nbt -> selector.get(nbt, newReferences::add));\n\n            if (newReferences.isEmpty()) {\n                return List.of();\n            }\n            references = newReferences;\n        }\n        return references;\n    }\n\n    /**\n     * Strings {@code source} through each selector, making each selector\n     * {@link Selector#prepare(NBTReference, Supplier) prepare} each element. This should result in this method\n     * returning nothing much less often, although it is still possible. Modifying the resulting NBT references will\n     * result in the provided {@code source} being modified.\n     * @param source the source, to get the NBT from\n     * @param finalDefault the default value for results produced from the last path selector\n     * @return the list of selected NBT, which may be empty\n     */\n    public @NotNull List<NBTReference> getWithDefaults(@NotNull NBTReference source, @NotNull Supplier<BinaryTag> finalDefault) {\n        List<NBTReference> references = List.of(source);\n\n        for (int selectorIndex = 0; selectorIndex < selectors().size(); selectorIndex++) {\n            var selector = selectors().get(selectorIndex);\n            Supplier<BinaryTag> next = (selectorIndex < selectors().size() - 1) ? selectors().get(selectorIndex + 1)::preparedNBT : finalDefault;\n\n            List<NBTReference> newNBT = new ArrayList<>();\n            for (var nbt : references) {\n                selector.prepare(nbt, next);\n                selector.get(nbt, newNBT::add);\n            }\n            if (newNBT.isEmpty()) {\n                return List.of();\n            }\n\n            references = newNBT;\n        }\n\n        return references;\n    }\n\n    /**\n     * Strings {@code source} through each selector, making each selector\n     * {@link Selector#prepare(NBTReference, Supplier) prepare} each element. This should result in this method\n     * returning nothing much less often, although it is still possible. Modifying the resulting NBT references will\n     * result in the provided {@code source} being modified. This is equivalent to calling\n     * {@link #getWithDefaults(NBTReference, Supplier)} and setting all of the results to {@code setValue}.\n     * @param source the source, to get the NBT from\n     * @param setValue the value to set all selected elements to\n     * @return the list of selected NBT, which may be empty\n     */\n    public @NotNull List<NBTReference> set(@NotNull NBTReference source, @NotNull BinaryTag setValue) {\n        List<NBTReference> references = getWithDefaults(source, () -> setValue);\n        for (var reference : references) {\n            reference.set(setValue);\n        }\n        return references;\n    }\n\n    @Override\n    public String toString() {\n        return selectors().stream().map(Selector::toString).collect(Collectors.joining());\n    }\n}\n\n@SuppressWarnings(\"UnstableApiUsage\")\nclass Parser {\n\n    static final @NotNull IntSet VALID_SELECTOR_STARTERS = IntSet.of('.', '{', '[');\n    static final @NotNull IntSet VALID_INTEGER_CHARACTERS = IntSet.of('-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');\n    static final @NotNull IntSet INVALID_UNQUOTED_CHARACTERS = IntSet.of(-1, '.', '\\'', '\\\"', '{', '}', '[', ']');\n\n    static final Codec<NBTPath> CODEC = Codec.STRING.transform(s -> {\n        try {\n            return readPath(new StringReader(s));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }, NBTPath::toString);\n\n    static @NotNull NBTPath readPath(@NotNull StringReader reader) throws IOException {\n        List<NBTPath.Selector> selectors = new ArrayList<>();\n\n        if (!VALID_SELECTOR_STARTERS.contains(peek(reader))) {\n            var key = readString(reader);\n            if (key != null) {\n                selectors.add(new NBTPath.Selector.RootKey(key));\n            }\n        }\n\n        while (true) {\n            reader.mark(0);\n\n            if (!VALID_SELECTOR_STARTERS.contains(peek(reader))) {\n                if (selectors.isEmpty()) {\n                    throw new IllegalArgumentException(\"NBT paths must contain at least one selector\");\n                }\n                return new NBTPath(List.copyOf(selectors));\n            }\n\n            var selector = readPathSelector(reader);\n            if (selector == null) {\n                throw new IllegalArgumentException(\"Invalid NBT path selector\");\n            }\n\n            selectors.add(selector);\n        }\n    }\n\n    // Returning null indicates a failure to read\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    static @Nullable NBTPath.Selector readPathSelector(@NotNull StringReader reader) throws IOException {\n        var firstChar = peek(reader);\n        return switch (firstChar) {\n            case '.' -> {\n                reader.skip(1); // Skip period\n\n                var string = readString(reader);\n                yield string != null ? new NBTPath.Selector.Key(string) : null;\n            }\n            case '{' -> {\n                var compound = readCompound(reader);\n                yield compound != null ? new NBTPath.Selector.CompoundFilter(compound) : null;\n            }\n            case '[' -> {\n                reader.skip(1); // Skip opening square brackets\n\n                var secondChar = peek(reader);\n                var selector = switch(secondChar) {\n                    case ']' -> new NBTPath.Selector.EntireList();\n                    case '{' -> {\n                        var compound = readCompound(reader);\n                        yield compound != null ? new NBTPath.Selector.ListFilter(compound) : null;\n                    }\n                    default -> {\n                        if (VALID_INTEGER_CHARACTERS.contains(secondChar)) {\n                            var index = readInteger(reader);\n                            yield index != null ? new NBTPath.Selector.Index(index) : null;\n                        }\n                        yield null;\n                    }\n                };\n\n                reader.skip(1); // Skip closing square brackets\n                yield selector;\n            }\n            default -> null;\n        };\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static @Nullable Integer readInteger(@NotNull StringReader reader) throws IOException {\n        StringBuilder builder = new StringBuilder();\n\n        int peek;\n        while (VALID_INTEGER_CHARACTERS.contains(peek = reader.read())) {\n            builder.appendCodePoint(peek);\n        }\n\n        // Unread the one extra character that was read; this does nothing if the entire string has been read\n        reader.skip(-1);\n\n        try {\n            return Integer.parseInt(builder.toString());\n        } catch (NumberFormatException e) {\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static @Nullable String readString(@NotNull StringReader reader) throws IOException {\n        var peek = peek(reader);\n        if (peek == -1) {\n            return null;\n        }\n\n        StringBuilder builder = new StringBuilder();\n\n        if (peek == '\"' || peek == '\\'') { // Read quoted string\n            reader.skip(1); // Skip the character we know already\n\n            boolean escape = false;\n\n            while (true) {\n                var next = reader.read();\n\n                if (next == '\\\\') { // Read escape character\n                    escape = true;\n                } else if (next == peek && !escape) { // Return if unescaped closing character\n                    return builder.toString();\n                } else {\n                    if (escape) { // If there was an unused escape, re-add it; there's only one character it's used for\n                        builder.appendCodePoint('\\\\');\n                    }\n\n                    if (next == -1) {\n                        return null;\n                    }\n\n                    builder.appendCodePoint(next); // Add the next character always\n                }\n            }\n        }\n\n        // Read unquoted string\n        int read;\n        while (!INVALID_UNQUOTED_CHARACTERS.contains(read = reader.read())) {\n            builder.appendCodePoint(read);\n        }\n\n        // Unread the one extra character that was read; this does nothing if the entire string has been read\n        reader.skip(-1);\n\n        return builder.isEmpty() ? null : builder.toString();\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static int peek(@NotNull StringReader reader) throws IOException {\n        var codePoint = reader.read();\n        reader.skip(-1);\n        return codePoint;\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private static BinaryTag readTag(@NotNull StringReader reader) throws IOException {\n        StringBuilder builder = new StringBuilder();\n\n        while (true) {\n            int code = reader.read();\n\n            if (code == -1) break;\n\n            builder.appendCodePoint(code);\n        }\n\n        String dump = builder.toString();\n\n        StringBuffer buffer = new StringBuffer();\n        BinaryTag tag = MinestomAdventure.tagStringIO().asTag(dump, buffer);\n\n        reader.skip(dump.length() - buffer.length()); // Skip (total chars) - (remaining chars) = read chars\n        return tag;\n    }\n\n    private static CompoundBinaryTag readCompound(@NotNull StringReader reader) throws IOException {\n        if (readTag(reader) instanceof CompoundBinaryTag compound) {\n            return compound;\n        } else {\n            throw new IllegalArgumentException(\"Expected an inline compound\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/nbt/NBTReference.java",
    "content": "package net.minestom.vanilla.loot.util.nbt;\n\nimport net.kyori.adventure.nbt.BinaryTag;\nimport net.kyori.adventure.nbt.BinaryTagTypes;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\nimport net.kyori.adventure.nbt.ListBinaryTag;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\npublic record NBTReference(@NotNull Supplier<BinaryTag> getter, @NotNull Consumer<BinaryTag> setter) {\n\n    public static @NotNull NBTReference of(@NotNull BinaryTag tag) {\n        AtomicReference<BinaryTag> reference = new AtomicReference<>(tag);\n        return new NBTReference(reference::get, reference::set);\n    }\n\n    public BinaryTag get() {\n        return getter.get();\n    }\n\n    public void set(BinaryTag nbt) {\n        setter.accept(nbt);\n    }\n\n    public boolean has(@NotNull String key) {\n        return get() instanceof CompoundBinaryTag compound && compound.get(key) != null;\n    }\n\n    public NBTReference get(@NotNull String key) {\n        return new NBTReference(\n                () -> get() instanceof CompoundBinaryTag compound ? compound.get(key) : null,\n                nbt -> {\n                    if (get() instanceof CompoundBinaryTag compound) {\n                        set(compound.put(key, nbt));\n                    }\n                }\n        );\n    }\n\n    public int listSize() {\n        return get() instanceof ListBinaryTag list ? list.size() : -1;\n    }\n\n    public @NotNull NBTReference get(int index) {\n        return new NBTReference(\n                () -> get() instanceof ListBinaryTag list && index >= 0 && index < list.size() ? list.get(index) : null,\n                value -> {\n                    if (get() instanceof ListBinaryTag list\n                            && (value.type().equals(BinaryTagTypes.END) || list.elementType().equals(value.type()))\n                            && index >= 0 && index < list.size()) {\n                        set(list.set(index, value, null));\n                    }\n                }\n        );\n    }\n\n    public void listAdd(@NotNull BinaryTag tag) {\n        if (get() instanceof ListBinaryTag list && (list.isEmpty() || list.elementType().equals(tag.type()))) {\n            set(list.add(tag));\n        }\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/nbt/NBTUtils.java",
    "content": "package net.minestom.vanilla.loot.util.nbt;\n\nimport net.kyori.adventure.nbt.*;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.CharBuffer;\nimport java.util.Map;\n\npublic class NBTUtils {\n\n    private NBTUtils() {}\n\n    /**\n     * Checks to see if everything in {@code standard} is contained in {@code comparison}. The comparison is allowed to\n     * have extra fields that are not contained in the standard.\n     * @param standard the standard that the comparison must have all elements of\n     * @param comparison the comparison, that is being compared against the standard. NBT compounds in this parameter,\n     *                   whether deeper in the tree or not, are allowed to have keys that the standard does not - it's\n     *                   basically compared against a standard.\n     * @param assureListOrder whether or not to assure list order. When true, lists are directly compared, but when\n     *                        false, the comparison is checked to see if it contains each item in the standard.\n     * @return true if the comparison fits the standard, otherwise false\n     */\n    public static boolean compareNBT(@Nullable BinaryTag standard, @Nullable BinaryTag comparison, boolean assureListOrder) {\n        if (standard == null) {\n            return true; // If there's no standard, it must always pass\n        } else if (comparison == null) {\n            return false; // If it's null at this point, we already assured that standard is null, so it must be invalid\n        } else if (!standard.type().equals(comparison.type())) {\n            return false; // If the classes aren't equal it can't fulfill the standard anyway\n        }\n        // If the list order is assured, it will be handled with the simple #equals call later in the method\n        if (!assureListOrder && standard instanceof ListBinaryTag guaranteeList) {\n            ListBinaryTag comparisonList = ((ListBinaryTag) comparison);\n            if (guaranteeList.isEmpty()) {\n                return comparisonList.isEmpty();\n            }\n            for (BinaryTag nbt : guaranteeList) {\n                boolean contains = false;\n                for (BinaryTag compare : comparisonList) {\n                    if (compareNBT(nbt, compare, false)) {\n                        contains = true;\n                        break;\n                    }\n                }\n                if (!contains) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        if (standard instanceof CompoundBinaryTag standardCompound) {\n            CompoundBinaryTag comparisonCompound = ((CompoundBinaryTag) comparison);\n            for (String key : standardCompound.keySet()) {\n                if (!compareNBT(comparisonCompound.get(key), comparisonCompound.get(key), assureListOrder)) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        return standard.equals(comparison);\n    }\n\n    /**\n     * Merges the two provided compounds, preferring the value of the {@code changes} compound and merging any nested\n     * NBT compounds like it would for the first-level ones.\n     * @param base the base compound, to be merged onto\n     * @param changes the changes to make to the base compound\n     * @return the merged compound\n     */\n    public static CompoundBinaryTag merge(@NotNull CompoundBinaryTag base, @NotNull CompoundBinaryTag changes) {\n        CompoundBinaryTag.Builder result = CompoundBinaryTag.builder();\n\n        result.put(base);\n\n        changes.iterator().forEachRemaining((entry) -> {\n            BinaryTag value = entry.getValue();\n\n            if (base.get(entry.getKey()) instanceof CompoundBinaryTag baseCompound\n                && entry.getValue() instanceof CompoundBinaryTag changeCompound) {\n                value = NBTUtils.merge(baseCompound, changeCompound);\n            }\n\n            result.put(entry.getKey(), value);\n        });\n\n        return result.build();\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/predicate/DamageSourcePredicate.java",
    "content": "package net.minestom.vanilla.loot.util.predicate;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.damage.DamageType;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.utils.Unit;\nimport org.jetbrains.annotations.NotNull;\n\n// TODO: Incomplete\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface DamageSourcePredicate {\n\n    @NotNull Codec<DamageSourcePredicate> CODEC = Codec.UNIT.transform(a -> (instance, pos, type) -> false, a -> Unit.INSTANCE);\n\n    boolean test(@NotNull Instance instance, @NotNull Point pos, @NotNull DamageType type);\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/predicate/EntityPredicate.java",
    "content": "package net.minestom.vanilla.loot.util.predicate;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.utils.Unit;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n// TODO: Incomplete\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface EntityPredicate {\n\n    @NotNull Codec<EntityPredicate> CODEC = Codec.UNIT.transform(a -> (instance, pos, entity) -> false, a -> Unit.INSTANCE);\n\n    boolean test(@NotNull Instance instance, @Nullable Point pos, @Nullable Entity entity);\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/predicate/ItemPredicate.java",
    "content": "package net.minestom.vanilla.loot.util.predicate;\n\nimport net.minestom.server.codec.StructCodec;\nimport net.minestom.server.instance.block.predicate.DataComponentPredicates;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.registry.Registries;\nimport net.minestom.server.registry.RegistryTag;\nimport net.minestom.vanilla.loot.LootContext;\nimport net.minestom.vanilla.loot.util.LootNumberRange;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic record ItemPredicate(@Nullable RegistryTag<Material> items, @NotNull LootNumberRange count, @NotNull DataComponentPredicates predicate) {\n\n    public static final @NotNull StructCodec<ItemPredicate> CODEC = StructCodec.struct(\n            \"items\", RegistryTag.codec(Registries::material).optional(), ItemPredicate::items,\n            \"count\", LootNumberRange.CODEC.optional(new LootNumberRange(null, null)), ItemPredicate::count,\n            StructCodec.INLINE, DataComponentPredicates.CODEC, ItemPredicate::predicate,\n            ItemPredicate::new\n    );\n\n    public boolean test(@NotNull ItemStack itemStack, @NotNull LootContext context) {\n\n        if (items != null && !items.contains(itemStack.material())) return false;\n\n        return count.check(context, itemStack.amount()) && false;\n        // TODO: Waiting for #2732\n        // return count.check(context, itemStack.amount()) && predicate.test(itemStack);\n    }\n\n}\n"
  },
  {
    "path": "loot-table/src/main/java/net/minestom/vanilla/loot/util/predicate/LocationPredicate.java",
    "content": "package net.minestom.vanilla.loot.util.predicate;\n\nimport net.minestom.server.codec.Codec;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.utils.Unit;\nimport org.jetbrains.annotations.NotNull;\n\n// TODO: Incomplete\n\n@SuppressWarnings(\"UnstableApiUsage\")\npublic interface LocationPredicate {\n\n    @NotNull Codec<LocationPredicate> CODEC = Codec.UNIT.transform(a -> (instance, point) -> false, a -> Unit.INSTANCE);\n\n    boolean test(@NotNull Instance instance, @NotNull Point point);\n\n}\n"
  },
  {
    "path": "mojang-data/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n}"
  },
  {
    "path": "mojang-data/src/main/java/io/github/pesto/MojangAssets.java",
    "content": "package io.github.pesto;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport net.minestom.vanilla.logging.Loading;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.concurrent.CompletableFuture;\n\nimport static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;\nimport static java.nio.file.StandardOpenOption.WRITE;\n\nfinal class MojangAssets {\n    private static final File ROOT = new File(\".\", \"mojang-data\");\n    private static final String VERSION_MANIFEST_URL = \"https://launchermeta.mojang.com/mc/game/version_manifest.json\";\n\n    public CompletableFuture<FileSystem<ByteArray>> getAssets(@NotNull String version) {\n        return CompletableFuture.supplyAsync(() -> downloadResources(version));\n    }\n\n    private FileSystem<ByteArray> downloadResources(@NotNull String version) {\n        try {\n            Loading.start(\"Downloading vanilla jar (\" + version + \")...\");\n\n            // Check if source files already exist\n            File jar = new File(ROOT, version + File.separator + \"resources.jar\");\n            if (!jar.exists()) {\n\n                // Get version info\n                String versionInfoUrl = findVersionInfoUrl(version);\n                JsonObject versionInfo = downloadJson(versionInfoUrl);\n\n                // Download jar\n                downloadJar(versionInfo, jar);\n            }\n\n            return FileSystem.fromZipFile(jar, path -> path.startsWith(\"data/minecraft/\")).folder(\"data\");\n\n        } catch (IOException e) {\n            exitError(e.getMessage());\n        } finally {\n            Loading.finish();\n        }\n        return FileSystem.empty();\n    }\n\n    /**\n     * Gets the version info from the version manifest\n     *\n     * @param version The release version or latest version if requested\n     * @return The url to the version info\n     * @throws IOException If the version info url could not be found\n     */\n    private String findVersionInfoUrl(@NotNull String version) throws IOException {\n        // Get manifest\n        JsonObject manifest = downloadJson(VERSION_MANIFEST_URL);\n\n        // Get the latest version if requested\n        if (version.equals(\"latest\")) {\n            JsonObject latest = manifest.getAsJsonObject(\"latest\");\n            version = latest.get(\"release\").getAsString();\n        }\n\n        // Find the url for the version's info\n        for (JsonElement elem : manifest.getAsJsonArray(\"versions\")) {\n            JsonObject info = elem.getAsJsonObject();\n            String id = info.get(\"id\").getAsString();\n\n            if (!id.equals(version))\n                continue;\n\n            return info.get(\"url\").getAsString();\n        }\n\n        throw new IOException(\"Failed to find version info url for version \" + version);\n    }\n\n    /**\n     * Downloads the vanilla jar to be used for extracting\n     *\n     * @param versionInfo The version info\n     */\n    private void downloadJar(JsonObject versionInfo, @NotNull File destination) throws IOException {\n        JsonObject downloads = versionInfo.getAsJsonObject(\"downloads\");\n        JsonObject client = downloads.getAsJsonObject(\"client\");\n        String url = client.get(\"url\").getAsString();\n\n        // Create if it doesn't exist\n        if (!destination.exists()) {\n            destination.getParentFile().mkdirs();\n            destination.createNewFile();\n        }\n\n        URLConnection connection = new URL(url).openConnection();\n        connection.connect();\n        try (InputStream input = connection.getInputStream()) {\n\n            // Download the jar to memory first\n            ByteBuffer buffer = ByteBuffer.allocateDirect(connection.getContentLength());\n\n            double totalMB = (double) connection.getContentLengthLong() / 1024 / 1024;\n            Loading.start(String.format(\"Downloading vanilla jar (%.2f MB)...\", totalMB));\n            long pos = 0;\n            long segmentCompleted = 0;\n            while (true) {\n                var bytes = input.readNBytes(64);\n                if (bytes.length == 0) break;\n                pos += bytes.length;\n                buffer.put(bytes);\n\n                // we only want to update the progress every 8th of the total size\n                double progress = (double) pos / (double) connection.getContentLengthLong();\n                if (progress - segmentCompleted > 1.0 / 8.0) {\n                    segmentCompleted = (long) (progress * 8.0) / 8;\n                    Loading.updater().progress(progress);\n                }\n            }\n            Loading.finish();\n\n            // Write the buffer to the file\n            buffer.flip();\n            try (FileChannel channel = FileChannel.open(destination.toPath(), WRITE, TRUNCATE_EXISTING)) {\n                channel.write(buffer);\n            }\n\n            boolean success = destination.exists() && destination.length() == connection.getContentLengthLong();\n            if (!success)\n                throw new IOException(\"Failed to download client JAR\");\n        }\n    }\n\n//    private boolean extractJarAssets(@NotNull File jarFile, File root) {\n//        File output = new File(root, jarFile.getParentFile().getName());\n//\n//        try (ZipInputStream zip = new ZipInputStream(new FileInputStream(jarFile))) {\n//            ZipEntry entry;\n//            while ((entry = zip.getNextEntry()) != null) {\n//\n//                // Skip unrelated entries\n//                if (!entry.getName().startsWith(\"data\"))\n//                    continue;\n//\n//                // Validate that the file exists\n//                File file = checkAndCreateFile(output, entry);\n//                if (file.exists()) continue;\n//\n//                if (entry.isDirectory()) {\n//                    if (!file.isDirectory() && !file.mkdirs())\n//                        throw new IOException(\"Failed to create directory \" + file);\n//                } else {\n//                    File parent = file.getParentFile();\n//                    if (!parent.isDirectory() && !parent.mkdirs())\n//                        throw new IOException(\"Failed to create directory \" + parent);\n//\n//                    Files.copy(zip, file.toPath());\n//                }\n//            }\n//            zip.closeEntry();\n//            jarFile.delete();\n//\n//            return true;\n//        } catch (IOException e) {\n//            e.printStackTrace();\n//        }\n//        return false;\n//    }\n\n    private JsonObject downloadJson(String url) throws IOException {\n        return JsonParser.parseReader(new InputStreamReader(getDownloadStream(url))).getAsJsonObject();\n    }\n\n    private InputStream getDownloadStream(String url) throws IOException {\n        return new URL(url).openStream();\n    }\n\n    private void exitError(String message) {\n        System.err.println(message);\n        System.exit(1);\n    }\n}"
  },
  {
    "path": "mojang-data/src/main/java/io/github/pesto/MojangDataFeature.java",
    "content": "package io.github.pesto;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.files.ByteArray;\nimport net.minestom.vanilla.files.FileSystem;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.concurrent.CompletableFuture;\n\npublic class MojangDataFeature implements VanillaReimplementation.Feature {\n\n    private static final String LATEST = \"1.21.5\";\n    private final MojangAssets assets = new MojangAssets();\n    private final CompletableFuture<FileSystem<ByteArray>> latest = new CompletableFuture<>();\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        assetsRequest(LATEST)\n                .thenAccept(latest::complete)\n                .join();\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"io_github_pesto:mojang_data\");\n    }\n\n    public FileSystem<ByteArray> latestAssets() {\n        if (!latest.isDone()) {\n            throw new IllegalStateException(\"Cannot request assets before {@link MojangDataFeature} is loaded\");\n        }\n        return latest.join();\n    }\n\n    public CompletableFuture<FileSystem<ByteArray>> assetsRequest(@NotNull String version) {\n        return assets.getAssets(version);\n    }\n}\n"
  },
  {
    "path": "server/build.gradle.kts",
    "content": "\n// Find all projects except for the root project and this project.\nval disallowed = setOf(project.name, project.parent!!.name)\nval includedProjects = (project.parent?.allprojects ?: emptyList()).filter { !disallowed.contains(it.name) }\n\ndependencies {\n    includedProjects.forEach {\n        api(project(\":\" + it.name))\n    }\n}\n\ntasks {\n    withType(com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class.java) {\n        minimize {\n            includedProjects.forEach {\n                exclude(project(\":\" + it.name))\n            }\n        }\n        manifest {\n            attributes(\n                \"Main-Class\" to \"net.minestom.vanilla.server.VanillaServer\",\n                \"Multi-Release\" to true\n            )\n        }\n        mergeServiceFiles()\n    }\n}"
  },
  {
    "path": "server/src/main/java/net/minestom/vanilla/server/VanillaDebug.java",
    "content": "package net.minestom.vanilla.server;\n\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.EntityType;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.player.PlayerChatEvent;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.VanillaRegistry;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.entitymeta.EntityTags;\n\nimport java.util.Random;\n\n/**\n * This debug server can be edited and committed to master without any consequences.\n * Make sure to keep anything cool you make! A quick comment is always a good idea.\n * <p>\n * And try not to remove anyone else's additions, just comment them out.\n */\npublic class VanillaDebug {\n    public static void hook(VanillaServer server) {\n        VanillaReimplementation vri = server.vri();\n        vri.process().eventHandler()\n                .addListener(PlayerChatEvent.class, event -> handleMessage(server, event.getPlayer(), event.getRawMessage()))\n//            .addListener(PlayerTickEvent.class, event -> {\n//                if (event.getPlayer().getInstance().getWorldAge() % 10 == 0) {\n//                    handleMessage(server, event.getPlayer(), \"fallingblock\");\n//                }\n//            })\n        ;\n    }\n\n    private static void handleMessage(VanillaServer server, Player player, String message) {\n        VanillaReimplementation vri = server.vri();\n        Block[] blocks = Block.values().toArray(new Block[0]);\n        Random random = new Random();\n        switch (message) {\n            case \"tnt\" -> {\n                Pos pos = player.getPosition();\n                VanillaRegistry.EntityContext context = vri.entityContext(EntityType.TNT, pos);\n                Entity entity = vri.createEntityOrDummy(context);\n                entity.setInstance(server.overworld(), pos);\n            }\n            case \"fallingblock\" -> {\n                Pos pos = player.getPosition();\n                VanillaRegistry.EntityContext context = vri.entityContext(EntityType.FALLING_BLOCK, pos,\n                        tags -> tags.setTag(EntityTags.FallingBlock.BLOCK, blocks[random.nextInt(blocks.length)]));\n                Entity entity = vri.createEntityOrDummy(context);\n                entity.setInstance(server.overworld(), pos);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "server/src/main/java/net/minestom/vanilla/server/VanillaEvents.java",
    "content": "package net.minestom.vanilla.server;\n\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.Entity;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventListener;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.instance.AddEntityToInstanceEvent;\nimport net.minestom.server.event.item.ItemDropEvent;\nimport net.minestom.server.event.item.PickupItemEvent;\nimport net.minestom.server.event.player.*;\nimport net.minestom.server.extras.MojangAuth;\nimport net.minestom.server.instance.ExplosionSupplier;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.inventory.PlayerInventory;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.network.ConnectionManager;\nimport net.minestom.server.network.packet.client.ClientPacket;\nimport net.minestom.server.network.packet.client.play.ClientPlayerPositionAndRotationPacket;\nimport net.minestom.server.network.packet.client.play.ClientPlayerPositionPacket;\nimport net.minestom.server.network.packet.client.play.ClientPlayerRotationPacket;\nimport net.minestom.server.utils.time.TimeUnit;\nimport net.minestom.vanilla.generation.VanillaTestGenerator;\nimport net.minestom.vanilla.instance.VanillaExplosion;\nimport net.minestom.vanilla.logging.Logger;\nimport net.minestom.vanilla.system.ServerProperties;\n\npublic class VanillaEvents {\n\n    public static void register(VanillaServer server, ServerProperties properties, EventNode<Event> eventNode) {\n        String worldName = properties.get(\"level-name\");\n\n        ExplosionSupplier explosionGenerator = (centerX, centerY, centerZ, strength, additionalData) -> {\n\n            boolean isTNT = additionalData != null && Boolean.TRUE.equals(additionalData.getBoolean(VanillaExplosion.DROP_EVERYTHING_KEY));\n            boolean noBlockDamage = additionalData != null && Boolean.TRUE.equals(additionalData.getBoolean(VanillaExplosion.DONT_DESTROY_BLOCKS_KEY));\n\n            return VanillaExplosion.builder(new Pos(centerX, centerY, centerZ), strength)\n                    .destroyBlocks(!noBlockDamage)\n                    .isFlaming(false)\n                    .dropEverything(isTNT)\n                    .build();\n        };\n        // TODO: World storage\n\n        VanillaTestGenerator noiseTestGenerator = new VanillaTestGenerator();\n        Instance overworld = server.overworld();\n//        overworld.enableAutoChunkLoad(true);\n        overworld.setGenerator(noiseTestGenerator);\n//        overworld.setExplosionSupplier(explosionGenerator);\n//        overworld.setChunkLoader(new AnvilChunkLoader(storageManager.getLocation(worldName + \"/region\")));\n//\n//        nether = MinecraftServer.getInstanceManager().createInstanceContainer(VanillaDimensionTypes.NETHER);\n//        nether.enableAutoChunkLoad(true);\n//        nether.setGenerator(noiseTestGenerator);\n//        nether.setExplosionSupplier(explosionGenerator);\n//        nether.setChunkLoader(new AnvilChunkLoader(storageManager.getLocation(worldName + \"/DIM-1/region\")));\n//\n//        end = MinecraftServer.getInstanceManager().createInstanceContainer(VanillaDimensionTypes.END);\n//        end.enableAutoChunkLoad(true);\n//        end.setGenerator(noiseTestGenerator);\n//        end.setExplosionSupplier(explosionGenerator);\n//        end.setChunkLoader(new AnvilChunkLoader(storageManager.getLocation(worldName + \"/DIM1/region\")));\n\n        // Load some chunks beforehand\n        int loopStart = -2;\n        int loopEnd = 2;\n        for (int x = loopStart; x < loopEnd; x++)\n            for (int z = loopStart; z < loopEnd; z++) {\n                overworld.loadChunk(x, z);\n            }\n\n        eventNode.addListener(AddEntityToInstanceEvent.class, event -> {\n            Entity entity = event.getEntity();\n\n            if (entity instanceof Player) {\n//                entity.setTag(NetherPortalBlockHandler.PORTAL_COOLDOWN_TIME_KEY, 5 * 20L);\n            }\n        });\n\n        MinecraftServer.getSchedulerManager().buildShutdownTask(() -> {\n            try {\n                overworld.saveInstance();\n            } catch (Throwable e) {\n                e.printStackTrace();\n            }\n        });\n\n        if (Boolean.parseBoolean(properties.get(\"online-mode\"))) {\n            MojangAuth.init();\n        }\n\n        ConnectionManager connectionManager = MinecraftServer.getConnectionManager();\n\n        eventNode.addListener(\n                EventListener.of(AsyncPlayerConfigurationEvent.class, event -> {\n                    event.setSpawningInstance(overworld);\n                    Logger.info(event.getPlayer().getUsername() + \" joined the server\");\n                })\n        );\n\n        eventNode.addListener(\n                EventListener.of(PlayerDisconnectEvent.class, event -> Logger.info(event.getPlayer().getUsername() + \" left the server\"))\n        );\n\n        eventNode.addListener(\n                PlayerPacketEvent.class,\n                event -> {\n                    ClientPacket packet = event.getPacket();\n                    // moving around and keepalive packets aren't usually required\n                    if (packet instanceof ClientPlayerPositionPacket) return;\n                    if (packet instanceof ClientPlayerPositionAndRotationPacket) return;\n                    if (packet instanceof ClientPlayerRotationPacket) return;\n                    Logger.debug(\"Packet received \" + event.getPacket());\n                }\n        );\n        eventNode.addListener(\n                PlayerPacketOutEvent.class,\n                playerPacketOutEvent -> {\n                    Logger.debug(\"Packet sent \" + playerPacketOutEvent.getPacket());\n                }\n        );\n\n        eventNode.addListener(PlayerMoveEvent.class, event -> {\n            Player player = event.getPlayer();\n            Point pos = player.getPosition();\n            Vec vel = player.getVelocity();\n\n            double currentX = pos.x();\n            double currentY = pos.y();\n            double currentZ = pos.z();\n            double velocityX = vel.x();\n            double velocityY = vel.y();\n            double velocityZ = vel.z();\n\n            Point newPos = event.getNewPosition();\n\n            double dx = newPos.x() - currentX;\n            double dy = newPos.y() - currentY;\n            double dz = newPos.z() - currentZ;\n\n            double actualDisplacement = dx * dx + dy * dy + dz * dz;\n            double expectedDisplacement = velocityX * velocityX + velocityY * velocityY + velocityZ * velocityZ;\n\n            float upperLimit = 100; // TODO: 300 if elytra deployed\n\n            if (actualDisplacement - expectedDisplacement >= upperLimit) {\n                event.setCancelled(true);\n                player.teleport(player.getPosition()); // force teleport to previous position\n                Logger.warn(player.getUsername() + \" moved too fast! \" + dx + \" \" + dy + \" \" + dz);\n            }\n        });\n\n        eventNode.addListener(\n                EventListener.builder(PlayerSpawnEvent.class)\n                        .filter(PlayerSpawnEvent::isFirstSpawn)\n                        .handler(event -> {\n                            Player player = event.getPlayer();\n\n                            player.setPermissionLevel(4);\n                            PlayerInventory inventory = player.getInventory();\n\n                            inventory.addItemStack(ItemStack.of(Material.OBSIDIAN, 1));\n                            inventory.addItemStack(ItemStack.of(Material.FLINT_AND_STEEL, 1));\n                            inventory.addItemStack(ItemStack.of(Material.RED_BED, 1));\n                            inventory.addItemStack(ItemStack.of(Material.CHEST, 1));\n                            inventory.addItemStack(ItemStack.of(Material.CRAFTING_TABLE, 1));\n                            inventory.addItemStack(ItemStack.of(Material.OAK_LOG, 32));\n                            inventory.addItemStack(ItemStack.of(Material.STONECUTTER, 1));\n                            inventory.addItemStack(ItemStack.of(Material.STONE, 64));\n\n                        })\n                        .build()\n        );\n\n        eventNode.addListener(\n                EventListener.builder(PickupItemEvent.class)\n                        .filter(event -> event.getEntity() instanceof Player)\n                        .handler(event -> {\n                            Player player = (Player) event.getEntity();\n                            boolean couldAdd = player.getInventory().addItemStack(event.getItemStack());\n                            event.setCancelled(!couldAdd); // Cancel event if player does not have enough inventory space\n                        })\n                        .build()\n        );\n\n        eventNode.addListener(ItemDropEvent.class, event -> {\n            Player player = event.getPlayer();\n            ItemStack droppedItem = event.getItemStack();\n\n            ItemEntity itemEntity = new ItemEntity(droppedItem);\n            itemEntity.setPickupDelay(500, TimeUnit.MILLISECOND);\n            itemEntity.setInstance(player.getInstance());\n            itemEntity.teleport(player.getPosition().add(0, 1.5f, 0));\n\n            Vec velocity = player.getPosition().direction().mul(6);\n            itemEntity.setVelocity(velocity);\n        });\n    }\n}\n"
  },
  {
    "path": "server/src/main/java/net/minestom/vanilla/server/VanillaServer.java",
    "content": "package net.minestom.vanilla.server;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.entity.GameMode;\nimport net.minestom.server.event.Event;\nimport net.minestom.server.event.EventNode;\nimport net.minestom.server.event.player.AsyncPlayerConfigurationEvent;\nimport net.minestom.server.extras.lan.OpenToLAN;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.network.ConnectionManager;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.dimensions.VanillaDimensionTypes;\nimport net.minestom.vanilla.logging.Level;\nimport net.minestom.vanilla.logging.Loading;\nimport net.minestom.vanilla.logging.Logger;\nimport net.minestom.vanilla.system.RayFastManager;\nimport net.minestom.vanilla.system.ServerProperties;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nclass VanillaServer {\n\n    /**\n     * A standard vanilla server launch used for testing purposes\n     *\n     * @param args arguments passed from console\n     */\n    public static void main(String[] args) {\n\n        // Use the static server process\n        MinecraftServer server = MinecraftServer.init();\n\n        // Use SETUP logging level since this is a standalone server.\n        Loading.level(Level.SETUP);\n        Logger.info(\"Setting up vri...\");\n        VanillaReimplementation vri = VanillaReimplementation.hook(MinecraftServer.process());\n\n        VanillaServer vanillaServer = new VanillaServer(server, vri, args);\n        Logger.info(\"Vanilla Reimplementation (%s) is setup.\", MinecraftServer.VERSION_NAME);\n\n        vanillaServer.start(\"0.0.0.0\", 25565);\n    }\n\n    private final MinecraftServer minecraftServer;\n    private final @NotNull ServerProperties serverProperties;\n\n    private final @NotNull VanillaReimplementation vri;\n\n    // Instances\n    private final @NotNull Instance overworld;\n\n    public VanillaServer(@NotNull MinecraftServer minecraftServer, @NotNull VanillaReimplementation vri,\n                         @Nullable String... args) {\n        this.minecraftServer = minecraftServer;\n        this.serverProperties = getOrGenerateServerProperties();\n        this.vri = vri;\n        this.overworld = vri.createInstance(Key.key(\"world\"), VanillaDimensionTypes.OVERWORLD);\n\n        // Try to get server properties\n\n        // Set up raycasting lib\n        RayFastManager.init();\n\n        EventNode<Event> eventHandler = MinecraftServer.getGlobalEventHandler();\n        ConnectionManager connectionManager = MinecraftServer.getConnectionManager();\n\n        DimensionType overworldDimension = VanillaDimensionTypes.OVERWORLD;\n\n        vri.process().scheduler().scheduleNextTick(OpenToLAN::open);\n\n        // Register systems\n        {\n            // Events\n            VanillaEvents.register(this, serverProperties, eventHandler);\n        }\n\n        overworld.loadChunk(0, 0).join();\n        int y = overworldDimension.maxY();\n        while (Block.AIR.compare(overworld.getBlock(0, y, 0))) {\n            y--;\n            if (y == overworldDimension.minY()) {\n                break;\n            }\n        }\n        int finalY = y;\n\n        vri.process().eventHandler()\n                .addListener(AsyncPlayerConfigurationEvent.class, event -> {\n                    event.setSpawningInstance(overworld);\n                    event.getPlayer().setGameMode(GameMode.SPECTATOR);\n                    event.getPlayer().setRespawnPoint(new Pos(0, finalY, 0));\n                });\n\n        MinecraftServer.getSchedulerManager().buildShutdownTask(() -> {\n            connectionManager.getOnlinePlayers().forEach(player -> {\n                // TODO: Saving\n                player.kick(\"Server is closing.\");\n                connectionManager.removePlayer(player.getPlayerConnection());\n            });\n            OpenToLAN.close();\n        });\n\n        // Preload chunks\n        long start = System.nanoTime();\n        int radius = MinecraftServer.getChunkViewDistance();\n        int total = radius * 2 * radius * 2;\n        Loading.start(\"Preloading \" + total + \" chunks\");\n        CompletableFuture<?>[] chunkFutures = new CompletableFuture[total];\n        AtomicInteger completed = new AtomicInteger(0);\n        for (int x = -radius; x < radius; x++) {\n            for (int z = -radius; z < radius; z++) {\n                int index = (x + radius) + (z + radius) * radius;\n                chunkFutures[index] = overworld.loadChunk(x, z)\n                        .thenRun(() -> {\n                            int completedCount = completed.incrementAndGet();\n                            Loading.updater().progress((double) completedCount / (double) chunkFutures.length);\n                        });\n            }\n        }\n        for (CompletableFuture<?> future : chunkFutures) {\n            if (future != null) future.join();\n        }\n        long end = System.nanoTime();\n        Loading.finish();\n        Logger.debug(\"Chunks per second: \" + (total / ((end - start) / 1e9)));\n\n        // Debug\n        if (List.of(args).contains(\"-debug\")) {\n            Logger.info(\"Debug mode enabled.\");\n            Logger.info(\"To disable it, remove the -debug argument\");\n            VanillaDebug.hook(this);\n        }\n    }\n\n    private ServerProperties getOrGenerateServerProperties() {\n        // TODO: Load from file correctly\n        try {\n            return new ServerProperties(\"\"\"\n                    #Minecraft server properties from a fresh 1.16.1 server\n                    #Generated on Mon Jul 13 17:23:48 CEST 2020\n                    spawn-protection=16\n                    max-tick-time=60000\n                    query.port=25565\n                    generator-settings=\n                    sync-chunk-writes=true\n                    force-gamemode=false\n                    allow-nether=true\n                    enforce-whitelist=false\n                    gamemode=survival\n                    broadcast-console-to-ops=true\n                    enable-query=false\n                    player-idle-timeout=0\n                    difficulty=easy\n                    broadcast-rcon-to-ops=true\n                    spawn-monsters=true\n                    op-permission-level=4\n                    pvp=true\n                    entity-broadcast-range-percentage=100\n                    snooper-enabled=true\n                    level-type=default\n                    enable-status=true\n                    hardcore=false\n                    enable-command-block=false\n                    max-players=20\n                    network-compression-threshold=256\n                    max-world-size=29999984\n                    resource-pack-sha1=\n                    function-permission-level=2\n                    rcon.port=25575\n                    server-port=25565\n                    server-ip=\n                    spawn-npcs=true\n                    allow-flight=false\n                    level-name=world\n                    view-distance=10\n                    resource-pack=\n                    spawn-animals=true\n                    white-list=false\n                    rcon.password=\n                    generate-structures=true\n                    online-mode=false\n                    max-build-height=256\n                    level-seed=\n                    prevent-proxy-connections=false\n                    use-native-transport=true\n                    enable-jmx-monitoring=false\n                    motd=A Minecraft Server\n                    enable-rcon=false\n                    \"\"\");\n        } catch (Throwable e) {\n            e.printStackTrace();\n            System.exit(1);\n            return null;\n        }\n    }\n\n    public void start(String address, int port) {\n        minecraftServer.start(address, port);\n    }\n\n    public VanillaReimplementation vri() {\n        return vri;\n    }\n\n    public Instance overworld() {\n        return overworld;\n    }\n}\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "rootProject.name = \"VanillaReimplementation\"\ninclude(\"core\")\ninclude(\"world-generation\")\ninclude(\"commands\")\ninclude(\"instance-meta\")\ninclude(\"block-update-system\")\ninclude(\"fluid-simulation\")\ninclude(\"item-placeables\")\ninclude(\"blocks\")\ninclude(\"entities\")\ninclude(\"entity-meta\")\ninclude(\"server\")\ninclude(\"items\")\ninclude(\"mojang-data\")\ninclude(\"crafting\")\ninclude(\"datapack-loading\")\ninclude(\"datapack-tests\")\ninclude(\"survival\")\ninclude(\"datapack\")\ninclude(\"loot-table\")\n\npluginManagement {\n    repositories {\n        mavenCentral()\n        maven(\"https://repo.spongepowered.org/repository/maven-public\")\n        maven(\"https://repo.spongepowered.org/repository/maven-snapshots\")\n    }\n}\n"
  },
  {
    "path": "survival/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":core\"))\n    api(project(\":datapack\"))\n    api(project(\":loot-table\"))\n    api(project(\":crafting\"))\n}"
  },
  {
    "path": "survival/src/main/java/net/minestom/vanilla/survival/Survival.java",
    "content": "package net.minestom.vanilla.survival;\n\nimport net.kyori.adventure.key.Key;\nimport net.kyori.adventure.text.Component;\nimport net.kyori.adventure.text.format.NamedTextColor;\nimport net.minestom.server.MinecraftServer;\nimport net.minestom.server.ServerProcess;\nimport net.minestom.server.component.DataComponents;\nimport net.minestom.server.coordinate.Pos;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.entity.ItemEntity;\nimport net.minestom.server.entity.Player;\nimport net.minestom.server.event.item.ItemDropEvent;\nimport net.minestom.server.event.item.PickupItemEvent;\nimport net.minestom.server.event.player.AsyncPlayerConfigurationEvent;\nimport net.minestom.server.event.player.PlayerDisconnectEvent;\nimport net.minestom.server.event.player.PlayerSpawnEvent;\nimport net.minestom.server.instance.Instance;\nimport net.minestom.server.instance.InstanceContainer;\nimport net.minestom.server.instance.LightingChunk;\nimport net.minestom.server.instance.WorldBorder;\nimport net.minestom.server.instance.anvil.AnvilLoader;\nimport net.minestom.server.item.ItemStack;\nimport net.minestom.server.item.Material;\nimport net.minestom.server.item.component.EnchantmentList;\nimport net.minestom.server.item.enchant.Enchantment;\nimport net.minestom.server.utils.time.TimeUnit;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.crafting.CraftingFeature;\nimport net.minestom.vanilla.crafting.Recipe;\nimport net.minestom.vanilla.logging.Logger;\nimport net.minestom.vanilla.loot.LootFeature;\nimport net.minestom.vanilla.loot.LootTable;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Map;\n\npublic class Survival {\n    public static void main(String[] args) {\n        // Initialize the server\n        MinecraftServer minecraftServer = MinecraftServer.init();\n\n        // Pass it to survival and initialize\n        new Survival(MinecraftServer.process()).initialize();\n        Logger.info(\"Initialized vanilla.\");\n\n        minecraftServer.start(\"0.0.0.0\", 25565);\n    }\n\n    private final @NotNull ServerProcess process;\n    private final @NotNull InstanceContainer overworld;\n\n    Survival(@NotNull ServerProcess process) {\n        this.process = process;\n\n        this.overworld = process.instance().createInstanceContainer(DimensionType.OVERWORLD);\n        this.overworld.setChunkLoader(new AnvilLoader(Path.of(\"world\")));\n        this.overworld.setChunkSupplier(LightingChunk::new);\n        this.overworld.setWorldBorder(new WorldBorder(\n                32 * 16 * 2, 0, 0, 10, 500, 560\n        ));\n    }\n\n    /**\n     * Initializes the server (event handlers, etc.).\n     */\n    public void initialize() {\n\n        Map<Key, LootTable> tables = LootFeature.buildFromDatapack(process);\n        process.eventHandler().addChild(LootFeature.createEventNode(tables));\n\n        Map<Key, Recipe> recipes = CraftingFeature.buildFromDatapack(process);\n        process.eventHandler().addChild(CraftingFeature.createEventNode(recipes, process));\n\n        process.eventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> {\n            final Player player = event.getPlayer();\n\n            // TODO: Determine respawn coordinates and radius, and then randomly pick a valid spot\n            Pos respawnPoint = new Pos(0, 64, 0, 0, 0);\n\n            event.setSpawningInstance(this.overworld);\n            player.setRespawnPoint(respawnPoint);\n\n            var enchs = new EnchantmentList(Map.of(\n                    Enchantment.EFFICIENCY, 5,\n                    Enchantment.FORTUNE, 3\n            ));\n\n            player.getInventory().addItemStack(ItemStack.of(Material.DIAMOND_PICKAXE).with(DataComponents.ENCHANTMENTS, enchs));\n            player.getInventory().addItemStack(ItemStack.of(Material.DIAMOND_HOE).with(DataComponents.ENCHANTMENTS, enchs));\n\n        }).addListener(PlayerSpawnEvent.class, event -> {\n            final Player player = event.getPlayer();\n\n            if (event.isFirstSpawn()) {\n                this.broadcast(Component.translatable(\"multiplayer.player.joined\", NamedTextColor.YELLOW).arguments(player.getName()));\n            }\n        }).addListener(PlayerDisconnectEvent.class, event -> {\n            final Player player = event.getPlayer();\n\n            this.broadcast(Component.translatable(\"multiplayer.player.left\", NamedTextColor.YELLOW).arguments(player.getName()));\n        }).addListener(PickupItemEvent.class, event -> {\n            if (!(event.getLivingEntity() instanceof Player player)) return;\n\n            // Cancel event if player does not have enough inventory space\n            ItemStack itemStack = event.getItemEntity().getItemStack();\n            event.setCancelled(!player.getInventory().addItemStack(itemStack));\n        }).addListener(ItemDropEvent.class, event -> {\n            final Player player = event.getPlayer();\n            ItemStack droppedItem = event.getItemStack();\n\n            Pos playerPos = player.getPosition();\n            ItemEntity itemEntity = new ItemEntity(droppedItem);\n            itemEntity.setPickupDelay(Duration.of(500, TimeUnit.MILLISECOND));\n            itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));\n            Vec velocity = playerPos.direction().mul(6);\n            itemEntity.setVelocity(velocity);\n        });\n    }\n\n    private void broadcast(@NotNull Component message) {\n        for (Instance instance : process.instance().getInstances()) {\n            instance.sendMessage(message);\n        }\n    }\n\n}\n"
  },
  {
    "path": "world-generation/build.gradle.kts",
    "content": "dependencies {\n    compileOnly(project(\":core\"))\n    compileOnly(project(\":datapack-loading\"))\n}"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/Aquifer.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.DoubleSupplier;\n\npublic interface Aquifer {\n\n    @Nullable Block compute(DensityFunction.Context context, double density);\n\n    record FluidStatus(int level, Block type) {\n        public Block at(int level) {\n            return level < this.level ? this.type : Block.AIR;\n        }\n    }\n\n    interface FluidPicker {\n        FluidStatus pickFluid(int x, int y, int z);\n    }\n\n    static Aquifer createDisabled(FluidPicker fluidPicker) {\n        return (context, density) -> {\n            if (density > 0) {\n                return null;\n            }\n            return fluidPicker.pickFluid(context.blockX(), context.blockY(), context.blockZ()).at(context.blockY());\n        };\n    }\n\n    class NoiseAquifer implements Aquifer {\n        private static final int X_SPACING = 16;\n        private static final int Y_SPACING = 12;\n        private static final int Z_SPACING = 16;\n\n        private static final int[][] SURFACE_SAMPLING = new int[][]{\n                {-2, -1}, {-1, -1}, {0, -1},\n                {1, -1}, {-3, 0}, {-2, 0},\n                {-1, 0}, {0, 0}, {1, 0},\n                {-2, 1}, {-1, 1}, {0, 1},\n                {1, 1}};\n\n        private final int minGridX;\n        private final int minGridY;\n        private final int minGridZ;\n        private final int gridSizeX;\n        private final int gridSizeZ;\n        private final int gridSize;\n\n        private final Map<Integer, FluidStatus> aquiferCache;\n        private final Map<Integer, Point> aquiferLocationCache;\n\n        private final NoiseChunk noiseChunk;\n        private final NoiseSettings.NoiseRouter router;\n        private final WorldgenRandom.Positional random;\n        private final FluidPicker globalFluidPicker;\n\n        public NoiseAquifer(\n                NoiseChunk noiseChunk,\n                Point chunkPos,\n                NoiseSettings.NoiseRouter router,\n                WorldgenRandom.Positional random,\n                int minY,\n                int height,\n                FluidPicker globalFluidPicker) {\n            this.noiseChunk = noiseChunk;\n            this.router = router;\n            this.random = random;\n            this.globalFluidPicker = globalFluidPicker;\n            this.minGridX = this.gridX(Util.chunkMinX(chunkPos)) - 1;\n            this.gridSizeX = this.gridX(Util.chunkMaxX(chunkPos)) + 1 - this.minGridX + 1;\n            this.minGridY = this.gridY(minY) - 1;\n            this.minGridZ = this.gridZ(Util.chunkMinZ(chunkPos)) - 1;\n            this.gridSizeZ = this.gridZ(Util.chunkMaxZ(chunkPos)) + 1 - this.minGridZ + 1;\n            int gridSizeY = this.gridY(minY + height) + 1 - this.minGridY + 1;\n            this.gridSize = this.gridSizeX * gridSizeY * this.gridSizeZ;\n            this.aquiferCache = new HashMap<>();\n            this.aquiferLocationCache = new HashMap<>();\n        }\n\n        public Block compute(DensityFunction.Context context, double density) {\n            int x = context.blockX();\n            int y = context.blockY();\n            int z = context.blockZ();\n            if (density <= 0) {\n                if (this.globalFluidPicker.pickFluid(x, y, z).at(y).compare(Block.LAVA)) {\n                    return Block.LAVA;\n                }\n\n                int gridX = this.gridX(x - 5);\n                int gridY = this.gridY(y + 1);\n                int gridZ = this.gridZ(z - 5);\n                double mag1 = Integer.MAX_VALUE;\n                double mag2 = Integer.MAX_VALUE;\n                double mag3 = Integer.MAX_VALUE;\n                Point loc1 = Vec.ZERO;\n                Point loc2 = Vec.ZERO;\n                Point loc3 = Vec.ZERO;\n\n                for (int xOffset = 0; xOffset <= 1; xOffset += 1) {\n                    for (int yOffset = -1; yOffset <= 1; yOffset += 1) {\n                        for (int zOffset = 0; zOffset <= 1; zOffset += 1) {\n                            Point location = this.getLocation(gridX + xOffset, gridY + yOffset, gridZ + zOffset);\n                            double magnitude = location.distanceSquared(Vec.ZERO);\n                            if (mag1 >= magnitude) {\n                                loc3 = loc2;\n                                loc2 = loc1;\n                                loc1 = location;\n                                mag3 = mag2;\n                                mag2 = mag1;\n                                mag1 = magnitude;\n                            } else if (mag2 >= magnitude) {\n                                loc3 = loc2;\n                                loc2 = location;\n                                mag3 = mag2;\n                                mag2 = magnitude;\n                            } else if (mag3 >= magnitude) {\n                                loc3 = location;\n                                mag3 = magnitude;\n                            }\n                        }\n                    }\n                }\n                FluidStatus status1 = this.getStatus(context, loc1);\n                FluidStatus status2 = this.getStatus(context, loc2);\n                FluidStatus status3 = this.getStatus(context, loc3);\n                double similarity12 = NoiseAquifer.similarity(mag1, mag2);\n                double similarity13 = NoiseAquifer.similarity(mag1, mag3);\n                double similarity23 = NoiseAquifer.similarity(mag2, mag3);\n\n                double pressure;\n                if (status1.at(y).compare(Block.WATER) && this.globalFluidPicker.pickFluid(x, y - 1, z).at(y - 1).compare(Block.LAVA)) {\n                    pressure = 1;\n                } else if (similarity12 > -1) {\n                    DoubleSupplier barrier = Util.lazyDouble(() -> this.router.barrier().compute(DensityFunction.context(x, y * 0.5, z)));\n                    double pressure12 = this.calculatePressure(y, status1, status2, barrier);\n                    double pressure13 = this.calculatePressure(y, status1, status3, barrier);\n                    double pressure23 = this.calculatePressure(y, status2, status3, barrier);\n                    double n = Math.max(Math.max(pressure12, pressure13 * Math.max(0, similarity13)), pressure23 * similarity23);\n                    pressure = Math.max(0, 2 * Math.max(0, similarity12) * n);\n                } else {\n                    pressure = 0;\n                }\n\n                if (density + pressure <= 0) {\n                    return status1.at(y);\n                }\n            }\n            return null;\n        }\n\n        private static double similarity(double a, double b) {\n            return 1 - Math.abs(b - a) / 25;\n        }\n\n        private double calculatePressure(int y, FluidStatus status1, FluidStatus status2, DoubleSupplier barrier) {\n            Block fluid1 = status1.at(y);\n            Block fluid2 = status2.at(y);\n            if ((fluid1.compare(Block.LAVA) && fluid2.compare(Block.WATER)) || (fluid1.compare(Block.WATER) && fluid2.compare(Block.LAVA))) {\n                return 1;\n            }\n            int levelDiff = Math.abs(status1.level - status2.level);\n            if (levelDiff == 0) {\n                return 0;\n            }\n            double levelAvg = (status1.level + status2.level) / 2.0;\n            double levelAvgDiff = y + 0.5 - levelAvg;\n            double p = levelDiff / 2.0 - Math.abs(levelAvgDiff);\n            double pressure = levelAvgDiff > 0\n                    ? p > 0 ? p / 1.5 : p / 2.5\n                    : p > -3 ? (p + 3) / 3 : (p + 3) / 10;\n            if (pressure < -2 || pressure > 2) {\n                return pressure;\n            }\n            return pressure + barrier.getAsDouble();\n        }\n\n        private FluidStatus getStatus(DensityFunction.Context context, Point location) {\n            int x = location.blockX();\n            int y = location.blockY();\n            int z = location.blockZ();\n            int index = this.getIndex(this.gridX(x), this.gridY(y), this.gridZ(z));\n            FluidStatus cachedStatus = this.aquiferCache.get(index);\n            if (cachedStatus != null) {\n                return cachedStatus;\n            }\n            FluidStatus status = this.computeStatus(context, x, y, z);\n            this.aquiferCache.put(index, status);\n            return status;\n        }\n\n        private FluidStatus computeStatus(DensityFunction.Context context, int x, int y, int z) {\n            FluidStatus globalStatus = this.globalFluidPicker.pickFluid(x, y, z);\n            int minPreliminarySurface = Integer.MIN_VALUE;\n            boolean isAquifer = false;\n            for (int[] offset : NoiseAquifer.SURFACE_SAMPLING) {\n                int xOffset = offset[0];\n                int zOffset = offset[1];\n                int blockX = x + (xOffset << 4);\n                int blockZ = z + (zOffset << 4);\n                int preliminarySurface = this.noiseChunk.getPreliminarySurfaceLevel(blockX, blockZ);\n                minPreliminarySurface = Math.min(minPreliminarySurface, preliminarySurface);\n                boolean noOffset = xOffset == 0 && zOffset == 0;\n                if (noOffset && y - 12 > preliminarySurface + 8) {\n                    return globalStatus;\n                }\n                if ((noOffset || y + 12 > preliminarySurface + 8)) {\n                    FluidStatus newStatus = this.globalFluidPicker.pickFluid(blockX, preliminarySurface + 8, blockZ);\n                    if (!newStatus.at(preliminarySurface + 8).compare(Block.AIR)) {\n                        if (noOffset) {\n                            return newStatus;\n                        } else {\n                            isAquifer = true;\n                        }\n                    }\n                }\n            }\n\n            double allowedFloodedness = isAquifer ? Util.clampedMap(minPreliminarySurface + 8 - y, 0, 64, 1, 0) : 0;\n            double floodedness = Util.clamp(this.router.fluid_level_floodedness().compute(DensityFunction.context(x, y * 0.67, z)), -1, 1);\n            if (floodedness > Util.map(allowedFloodedness, 1, 0, -0.3, 0.8)) {\n                return globalStatus;\n            }\n            if (floodedness <= Util.map(allowedFloodedness, 1, 0, -0.8, 0.4)) {\n                return new FluidStatus(Integer.MIN_VALUE, globalStatus.type);\n            }\n\n            int gridY = (int) Math.floor(y / 40);\n            double spread = this.router.fluid_level_spread().compute(DensityFunction.context(Math.floor(x / 16), gridY, Math.floor(z / 16)));\n            int level = gridY * 40 + 20 + (int) Math.floor(spread / 3) * 3;\n            int statusLevel = Math.min(minPreliminarySurface, level);\n            Block fluid = this.getFluidType(context, x, y, z, globalStatus.type, level);\n            return new FluidStatus(statusLevel, fluid);\n        }\n\n        private Block getFluidType(DensityFunction.Context context, double x, double y, double z, Block global, int level) {\n            if (level <= -10) {\n                double lava = this.router.lava().compute(DensityFunction.context(Math.floor(x / 64), Math.floor(y / 40), Math.floor(z / 64)));\n                if (Math.abs(lava) > 0.3) {\n                    return Block.LAVA;\n                }\n            }\n            return global;\n        }\n\n        private Point getLocation(int x, int y, int z) {\n            int index = this.getIndex(x, y, z);\n            Point cachedLocation = this.aquiferLocationCache.get(index);\n            if (Vec.ZERO.equals(cachedLocation)) {\n                return cachedLocation;\n            }\n            WorldgenRandom random = this.random.at(x, y, z);\n            Point location = new Vec(\n                    x * NoiseAquifer.X_SPACING + random.nextInt(10),\n                    y * NoiseAquifer.Y_SPACING + random.nextInt(9),\n                    z * NoiseAquifer.Z_SPACING + random.nextInt(10));\n            this.aquiferLocationCache.put(index, location);\n            return location;\n        }\n\n        private int getIndex(int x, int y, int z) {\n            int gridX = x - this.minGridX;\n            int gridY = y - this.minGridY;\n            int gridZ = z - this.minGridZ;\n            int index = (gridY * this.gridSizeZ + gridZ) * this.gridSizeX + gridX;\n            if (index < 0 || index >= this.gridSize) {\n                throw new Error(\"Invalid aquifer index at (\" + x + \", \" + y + \", \" + z + \") : 0 <= \" + index + \" < \" + gridSize);\n            }\n            return index;\n        }\n\n        private int gridX(int x) {\n            return (int) Math.floor(x / NoiseAquifer.X_SPACING);\n        }\n\n        private int gridY(int y) {\n            return (int) Math.floor(y / NoiseAquifer.Y_SPACING);\n        }\n\n        private int gridZ(int z) {\n            return (int) Math.floor(z / NoiseAquifer.Z_SPACING);\n        }\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunk.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport net.minestom.server.coordinate.CoordConversion;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class NoiseChunk {\n    public final int cellWidth;\n    public final int cellHeight;\n    public final int firstCellX;\n    public final int firstCellZ;\n    public final double firstNoiseX;\n    public final double firstNoiseZ;\n    public final double noiseSizeXZ;\n    private final Map<Long, Integer> preliminarySurfaceLevel = new HashMap<>();\n    private final Aquifer aquifer;\n    private final MaterialRule materialRule;\n    private final DensityFunction initialDensity;\n\n    public int cellCountXZ;\n    public int cellCountY;\n    public int cellNoiseMinY;\n\n    public int minX;\n    public int minZ;\n    public NoiseSettings settings;\n\n    public NoiseChunk(\n            int cellCountXZ,\n            int cellCountY,\n            int cellNoiseMinY,\n            RandomState randomState,\n            int minX,\n            int minZ,\n            NoiseSettings settings,\n            boolean aquifersEnabled,\n            Aquifer.FluidPicker fluidPicker) {\n        this.cellWidth = NoiseSettings.cellWidth(settings);\n        this.cellHeight = NoiseSettings.cellHeight(settings);\n        this.firstCellX = (int) (double) (minX / this.cellWidth);\n        this.firstCellZ = (int) (double) (minZ / this.cellWidth);\n        this.firstNoiseX = minX >> 2;\n        this.firstNoiseZ = minZ >> 2;\n        this.noiseSizeXZ = (cellCountXZ * this.cellWidth) >> 2;\n\n        if (true) { // WIP: Noise aquifers don't work yet\n            this.aquifer = Aquifer.createDisabled(fluidPicker);\n        } else {\n            Point chunkPos = new Vec(minX, 0, minZ);\n            int minY = cellNoiseMinY * NoiseSettings.cellHeight(settings);\n            int height = cellCountY * NoiseSettings.cellHeight(settings);\n            this.aquifer = new Aquifer.NoiseAquifer(this, chunkPos, randomState.router, randomState.aquiferRandom, minY, height, fluidPicker);\n        }\n        DensityFunction finalDensity = randomState.router.final_density();\n        this.materialRule = MaterialRule.fromList(List.of(\n                (context) -> this.aquifer.compute(context, finalDensity.compute(context))\n        ));\n        this.initialDensity = randomState.router.initial_density_without_jaggedness();\n    }\n\n    public @Nullable Block getFinalState(Datapack datapack, int x, int y, int z) {\n        return this.materialRule.compute(DensityFunction.context(x, y, z));\n    }\n\n    public int getPreliminarySurfaceLevel(int quartX, int quartZ) {\n        return preliminarySurfaceLevel.computeIfAbsent(CoordConversion.chunkIndex(quartX, quartZ), (key) -> {\n            int x = quartX << 2;\n            int z = quartZ << 2;\n            for (int y = this.settings.noise().min_y() + this.settings.noise().height(); y >= this.settings.noise().min_y(); y -= this.cellHeight) {\n                double density = this.initialDensity.compute(DensityFunction.context(x, y, z));\n                if (density > 0.390625) {\n                    return y;\n                }\n            }\n            return Integer.MIN_VALUE;\n        });\n    }\n\n    public interface MaterialRule {\n        @Nullable Block compute(DensityFunction.Context context);\n\n        static MaterialRule fromList(List<MaterialRule> rules) {\n            return (context) -> {\n                for (MaterialRule rule : rules) {\n                    Block state = rule.compute(context);\n                    if (state != null) return state;\n                }\n                return null;\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunkGenerator.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.CoordConversion;\nimport net.minestom.server.instance.Chunk;\nimport net.minestom.server.instance.batch.ChunkBatch;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.world.DimensionType;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.WorldgenContext;\nimport net.minestom.vanilla.datapack.worldgen.biome.BiomeSource;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.UnknownNullability;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NoiseChunkGenerator {\n    private final Map<Long, NoiseChunk> noiseChunkCache = new HashMap<>();\n    private final Aquifer.FluidPicker globalFluidPicker;\n\n//    constructor(\n//                    private readonly biomeSource: BiomeSource,\n//                    private readonly settings: NoiseGeneratorSettings,\n//                    ) {\n//        this.noiseChunkCache = new Map()\n//\n//    const lavaFluid = new FluidStatus(-54, BlockState.LAVA)\n//    const defaultFluid = new FluidStatus(settings.seaLevel, settings.defaultFluid)\n//        this.globalFluidPicker = (x, y, z) => {\n//            if (y < Math.min(-54, settings.seaLevel)) {\n//                return lavaFluid\n//            }\n//            return defaultFluid\n//        }\n//    }\n\n    private final @NotNull Datapack datapack;\n    private final @NotNull BiomeSource biomeSource;\n    private final @NotNull NoiseSettings settings;\n\n\n    // Minestom\n    private final DimensionType dimensionType;\n\n    public NoiseChunkGenerator(@NotNull Datapack datapack, @NotNull BiomeSource biomeSource, @NotNull NoiseSettings settings, DimensionType dimensionType) {\n        this.datapack = datapack;\n        this.biomeSource = biomeSource;\n        this.settings = settings;\n        this.dimensionType = dimensionType;\n\n        Aquifer.FluidStatus lavaFluid = new Aquifer.FluidStatus(-54, Block.LAVA);\n        Aquifer.FluidStatus defaultFluid = new Aquifer.FluidStatus(settings.sea_level(), settings.default_fluid().toMinestom());\n        this.globalFluidPicker = (x, y, z) -> {\n            if (y < Math.min(-54, settings.sea_level())) {\n                return lavaFluid;\n            }\n            return defaultFluid;\n        };\n    }\n\n//    public fill(randomState: RandomState, chunk: Chunk, onlyFirstZ: boolean = false) {\n//        const minY = Math.max(chunk.minY, this.settings.noise.minY)\n//        const maxY = Math.min(chunk.maxY, this.settings.noise.minY + this.settings.noise.height)\n//\n//        const cellWidth = NoiseSettings.cellWidth(this.settings.noise)\n//        const cellHeight = NoiseSettings.cellHeight(this.settings.noise)\n//        const cellCountXZ = Math.floor(16 / cellWidth)\n//\n//        const minCellY = Math.floor(minY / cellHeight)\n//        const cellCountY = Math.floor((maxY - minY) / cellHeight)\n//\n//        const minX = ChunkPos.minBlockX(chunk.pos)\n//        const minZ = ChunkPos.minBlockZ(chunk.pos)\n//\n//        const noiseChunk = this.getOrCreateNoiseChunk(randomState, chunk)\n\n    public void fill(Datapack datapack, RandomState randomState, TargetChunk chunk) {\n        fill(datapack, randomState, chunk, false);\n    }\n\n    public void fill(Datapack datapack, RandomState randomState, TargetChunk chunk, boolean onlyFirstZ) {\n        int minY = Math.max(chunk.minY(), this.settings.noise().min_y());\n        int maxY = Math.min(chunk.maxY(), this.settings.noise().min_y() + this.settings.noise().height());\n\n        int cellWidth = NoiseSettings.cellWidth(this.settings);\n        int cellHeight = NoiseSettings.cellHeight(this.settings);\n        int cellCountXZ = Math.floorDiv(16, cellWidth);\n\n        int minCellY = Math.floorDiv(minY, cellHeight);\n        int cellCountY = Math.floorDiv(maxY - minY, cellHeight);\n\n        NoiseChunk noiseChunk = this.getOrCreateNoiseChunk(randomState, chunk);\n\n        for (int cellX = 0; cellX < cellCountXZ; cellX += 1) {\n            for (int cellZ = 0; cellZ < (onlyFirstZ ? 1 : cellCountXZ); cellZ += 1) {\n                for (int cellY = cellCountY - 1; cellY >= 0; cellY -= 1) {\n                    for (int offY = cellHeight - 1; offY >= 0; offY -= 1) {\n                        int blockY = (minCellY + cellY) * cellHeight + offY;\n                        int sectionY = blockY / Chunk.CHUNK_SECTION_SIZE;\n\n                        for (int offX = 0; offX < cellWidth; offX += 1) {\n                            int blockX = chunk.minX() + cellX * cellWidth + offX;\n                            int sectionX = blockX & 0xF;\n\n                            for (int offZ = 0; offZ < (onlyFirstZ ? 1 : cellWidth); offZ += 1) {\n                                int blockZ = chunk.minZ() + cellZ * cellWidth + offZ;\n                                int sectionZ = blockZ & 0xF;\n\n                                Block state = noiseChunk.getFinalState(datapack, blockX, blockY, blockZ);\n                                if (state == null) {\n                                    state = this.settings.default_block().toMinestom();\n                                }\n                                chunk.setBlock(blockX, blockY, blockZ, state);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    //    public buildSurface(randomState: RandomState, chunk: Chunk, /** @deprecated */ biome: string = 'minecraft:plains') {\n//        const noiseChunk = this.getOrCreateNoiseChunk(randomState, chunk)\n//        const context = WorldgenContext.create(this.settings.noise.minY, this.settings.noise.height)\n//        randomState.surfaceSystem.buildSurface(chunk, noiseChunk, context, () => biome)\n//    }\n    public void buildSurface(Datapack datapack, RandomState randomState, TargetChunk chunk, Key biome) {\n        NoiseChunk noiseChunk = this.getOrCreateNoiseChunk(randomState, chunk);\n        WorldgenContext context = WorldgenContext.create(this.dimensionType);\n        randomState.surfaceSystem.buildSurface(chunk, noiseChunk, context, point -> biome);\n    }\n\n    public Key computeBiome(RandomState randomState, int quartX, int quartY, int quartZ) {\n        return this.biomeSource.getBiome(quartX, quartY, quartZ, randomState.sampler);\n    }\n\n    private NoiseChunk getOrCreateNoiseChunk(RandomState randomState, TargetChunk chunk) {\n        return this.noiseChunkCache.computeIfAbsent(chunk.index(), ignored -> {\n//            const minY = Math.max(chunk.minY, this.settings.noise.minY)\n//            const maxY = Math.min(chunk.maxY, this.settings.noise.minY + this.settings.noise.height)\n//\n//            const cellWidth = NoiseSettings.cellWidth(this.settings.noise)\n//            const cellHeight = NoiseSettings.cellHeight(this.settings.noise)\n//            const cellCountXZ = Math.floor(16 / cellWidth)\n//\n//            const minCellY = Math.floor(minY / cellHeight)\n//            const cellCountY = Math.floor((maxY - minY) / cellHeight)\n//            const minX = ChunkPos.minBlockX(chunk.pos)\n//            const minZ = ChunkPos.minBlockZ(chunk.pos)\n//\n//            return new NoiseChunk(cellCountXZ, cellCountY, minCellY, randomState, minX, minZ, this.settings.noise, this.settings.aquifersEnabled, this.globalFluidPicker)\n            int minY = Math.max(chunk.minY(), this.settings.noise().min_y());\n            int maxY = Math.min(chunk.maxY(), this.settings.noise().min_y() + this.settings.noise().height());\n\n            int cellWidth = NoiseSettings.cellWidth(this.settings);\n            int cellHeight = NoiseSettings.cellHeight(this.settings);\n            int cellCountXZ = Math.floorDiv(Chunk.CHUNK_SECTION_SIZE, cellWidth);\n\n            int minCellY = Math.floorDiv(minY, cellHeight);\n            int cellCountY = Math.floorDiv(maxY - minY, cellHeight);\n            int minX = chunk.minX();\n            int minZ = chunk.minZ();\n\n            return new NoiseChunk(cellCountXZ, cellCountY, minCellY, randomState, minX, minZ, this.settings, this.settings.aquifers_enabled(), this.globalFluidPicker);\n        });\n    }\n\n    public synchronized void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {\n        TargetChunkImpl chunk = new TargetChunkImpl(batch,\n                chunkX, chunkZ,\n                dimensionType.minY() / Chunk.CHUNK_SECTION_SIZE,\n                dimensionType.maxY() / Chunk.CHUNK_SECTION_SIZE);\n        RandomState randomState = new RandomState(settings, 125);\n        fill(this.datapack, randomState, chunk);\n    }\n\n    private static class TargetChunkImpl implements TargetChunk {\n\n        private final int chunkX;\n        private final int chunkZ;\n\n        private final int minSection;\n        private final int maxSection;\n\n        private final ChunkBatch batch;\n\n        private final Int2ObjectMap<Block> blocks = new Int2ObjectOpenHashMap<>();\n\n        public TargetChunkImpl(ChunkBatch batch, int chunkX, int chunkZ, int minSection, int maxSection) {\n            this.chunkX = chunkX;\n            this.chunkZ = chunkZ;\n            this.minSection = minSection;\n            this.maxSection = maxSection;\n            this.batch = batch;\n        }\n\n        @Override\n        public int chunkX() {\n            return this.chunkX;\n        }\n\n        @Override\n        public int chunkZ() {\n            return this.chunkZ;\n        }\n\n        @Override\n        public int minSection() {\n            return this.minSection;\n        }\n\n        @Override\n        public int maxSection() {\n            return this.maxSection;\n        }\n\n        @Override\n        public @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) {\n            int index = CoordConversion.chunkBlockIndex(x, y, z);\n            return this.blocks.getOrDefault(index, Block.STONE);\n        }\n\n        @Override\n        public void setBlock(int x, int y, int z, @NotNull Block block) {\n            if (x < minX() || x >= maxX() || y < minY() || y >= maxY() || z < minZ() || z >= maxZ()) {\n                return;\n            }\n            int index = CoordConversion.chunkBlockIndex(x, y, z);\n            this.blocks.put(index, block);\n            batch.setBlock(x - minX(), y, z - minZ(), block);\n        }\n    }\n\n    public interface TargetChunk extends Block.Getter, Block.Setter {\n        int chunkX();\n\n        int chunkZ();\n\n        default int minX() {\n            return chunkX() * Chunk.CHUNK_SIZE_X;\n        }\n\n        default int maxX() {\n            return minX() + Chunk.CHUNK_SIZE_X;\n        }\n\n        default int minZ() {\n            return chunkZ() * Chunk.CHUNK_SIZE_Z;\n        }\n\n        default int maxZ() {\n            return minZ() + Chunk.CHUNK_SIZE_Z;\n        }\n\n        default long index() {\n            return CoordConversion.chunkIndex(chunkX(), chunkZ());\n        }\n\n        int minSection();\n\n        int maxSection();\n\n        default int minY() {\n            return minSection() * Chunk.CHUNK_SECTION_SIZE;\n        }\n\n        default int maxY() {\n            return (maxSection() + 1) * Chunk.CHUNK_SECTION_SIZE;\n        }\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/RandomState.java",
    "content": "package net.minestom.vanilla.generation;\n\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.biome.Climate;\nimport net.minestom.vanilla.datapack.worldgen.random.LegacyRandom;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.random.XoroshiroRandom;\n\npublic class RandomState {\n\n    public final WorldgenRandom.Positional random;\n    public final WorldgenRandom.Positional aquiferRandom;\n    public final WorldgenRandom.Positional oreRandom;\n    public final SurfaceSystem surfaceSystem;\n    public final NoiseSettings.NoiseRouter router;\n    public final Climate.Sampler sampler;\n\n    public final long seed;\n\n    public RandomState(NoiseSettings settings, long seed) {\n        this.seed = seed;\n        this.random = (settings.legacy_random_source() ? new LegacyRandom(seed) : new XoroshiroRandom(seed)).forkPositional();\n        this.aquiferRandom = this.random.fromHashOf(Key.key(\"aquifer\").toString()).forkPositional();\n        this.oreRandom = this.random.fromHashOf(Key.key(\"ore\").toString()).forkPositional();\n        this.surfaceSystem = new SurfaceSystem(settings.surface_rule(), settings.default_block().toMinestom(), seed);\n        this.router = settings.noise_router();\n        this.sampler = Climate.Sampler.fromRouter(this.router);\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/SurfaceContext.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.world.biome.Biome;\nimport net.minestom.vanilla.datapack.worldgen.DensityFunction;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.WorldgenContext;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.util.Util;\n\nimport java.util.function.Function;\nimport java.util.function.IntSupplier;\nimport java.util.function.Supplier;\n\npublic class SurfaceContext implements NoiseSettings.SurfaceRule.Context {\n    public int blockX;\n    public int blockY;\n    public int blockZ;\n    public int stoneDepthAbove;\n    public int stoneDepthBelow;\n    public int surfaceDepth;\n    public int waterHeight;\n\n    public Supplier<Key> fetchBiome = Biome.PLAINS::key;\n    public IntSupplier surfaceSecondary = () -> 0;\n    public IntSupplier minSurfaceLevel = () -> 0;\n\n    public final SurfaceSystem system;\n    public final NoiseChunkGenerator.TargetChunk chunk;\n    public final NoiseChunk noiseChunk;\n    public final WorldgenContext context;\n    private final Function<Point, Key> getBiome;\n\n    public SurfaceContext(SurfaceSystem system, NoiseChunkGenerator.TargetChunk chunk, NoiseChunk noiseChunk, WorldgenContext context,\n                   Function<Point, Key> getBiome) {\n        this.system = system;\n        this.chunk = chunk;\n        this.noiseChunk = noiseChunk;\n        this.context = context;\n        this.getBiome = getBiome;\n    }\n\n    public void updateXZ(int x, int z) {\n        this.blockX = x;\n        this.blockZ = z;\n        this.surfaceDepth = this.system.getSurfaceDepth(x, z);\n        this.surfaceSecondary = Util.lazyInt(() -> (int) this.system.getSurfaceSecondary(x, z));\n        this.minSurfaceLevel = Util.lazyInt(() -> this.calculateMinSurfaceLevel(x, z));\n    }\n\n    public void updateY(int stoneDepthAbove, int stoneDepthBelow, int waterHeight, int y) {\n        this.blockY = y;\n        this.stoneDepthAbove = stoneDepthAbove;\n        this.stoneDepthBelow = stoneDepthBelow;\n        this.waterHeight = waterHeight;\n        this.fetchBiome = Util.lazy(() -> this.getBiome.apply(new Vec(this.blockX, this.blockY, this.blockZ)));\n    }\n\n    private int calculateMinSurfaceLevel(int x, int z) {\n        int cellX = x >> 4;\n        int cellZ = z >> 4;\n        int level00 = this.noiseChunk.getPreliminarySurfaceLevel(cellX << 4, cellZ << 4);\n        int level10 = this.noiseChunk.getPreliminarySurfaceLevel((cellX + 1) << 4, cellZ << 4);\n        int level01 = this.noiseChunk.getPreliminarySurfaceLevel(cellX << 4, (cellZ + 1) << 4);\n        int level11 = this.noiseChunk.getPreliminarySurfaceLevel((cellX + 1) << 4, (cellZ + 1) << 4);\n        int level = (int) Math.floor(Util.lerp2((double) (x & 0xF) / 16, (double) (z & 0xF) / 16, level00, level10, level01, level11));\n        return level + this.surfaceDepth - 8;\n    }\n\n    private DensityFunction.Context asDFContext() {\n        return DensityFunction.context(this.blockX, this.blockY, this.blockZ);\n    }\n\n    @Override\n    public Key biome() {\n        return this.fetchBiome.get();\n    }\n\n    @Override\n    public int minY() {\n        return this.blockY - this.stoneDepthAbove;\n    }\n\n    @Override\n    public int maxY() {\n        return context.minY();\n    }\n\n    @Override\n    public int blockX() {\n        return this.blockX;\n    }\n\n    @Override\n    public int blockY() {\n        return this.blockY;\n    }\n\n    @Override\n    public int blockZ() {\n        return this.blockZ;\n    }\n\n    @Override\n    public WorldgenRandom random(String string) {\n        return this.system.getRandom(string);\n    }\n\n    @Override\n    public int stoneDepthAbove() {\n        return this.stoneDepthAbove;\n    }\n\n    @Override\n    public int surfaceDepth() {\n        return this.surfaceDepth;\n    }\n\n    @Override\n    public int waterHeight() {\n        return this.waterHeight;\n    }\n\n    @Override\n    public int minSurfaceLevel() {\n        return this.minSurfaceLevel.getAsInt();\n    }\n\n    @Override\n    public int stoneDepthBelow() {\n        return this.stoneDepthBelow;\n    }\n\n    @Override\n    public double surfaceSecondary() {\n        return this.surfaceSecondary.getAsInt();\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/SurfaceSystem.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.instance.Chunk;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.datapack.worldgen.WorldgenContext;\nimport net.minestom.vanilla.datapack.worldgen.WorldgenRegistries;\nimport net.minestom.vanilla.datapack.worldgen.noise.NormalNoise;\nimport net.minestom.vanilla.datapack.worldgen.random.WorldgenRandom;\nimport net.minestom.vanilla.datapack.worldgen.random.XoroshiroRandom;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Function;\n\npublic class SurfaceSystem {\n    private final NormalNoise surfaceNoise;\n    private final NormalNoise surfaceSecondaryNoise;\n    private final WorldgenRandom.Positional random;\n    private final Map<String, WorldgenRandom> positionalRandoms;\n\n    private final NoiseSettings.SurfaceRule rule;\n    private final Block defaultBlock;\n\n    public SurfaceSystem(NoiseSettings.SurfaceRule rule, Block defaultBlock, long seed) {\n        this.random = new XoroshiroRandom(seed).forkPositional();\n        this.surfaceNoise = NoiseSettings.NoiseRouter.instantiate(this.random, WorldgenRegistries.SURFACE_NOISE);\n        this.surfaceSecondaryNoise = NoiseSettings.NoiseRouter.instantiate(this.random, WorldgenRegistries.SURFACE_SECONDARY_NOISE);\n        this.positionalRandoms = new HashMap<>();\n        this.rule = rule;\n        this.defaultBlock = defaultBlock;\n    }\n\n    public void buildSurface(NoiseChunkGenerator.TargetChunk chunk, NoiseChunk noiseChunk, WorldgenContext context, Function<Point, Key> getBiome) {\n        int minX = chunk.minX();\n        int minZ = chunk.minZ();\n        int minY = chunk.minY();\n        int maxY = chunk.maxY();\n        SurfaceContext surfaceContext = new SurfaceContext(this, chunk, noiseChunk, context, getBiome);\n        var ruleWithContext = this.rule.apply(surfaceContext);\n\n        for (int x = 0; x < Chunk.CHUNK_SIZE_X; x += 1) {\n            int worldX = minX + x;\n            for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z += 1) {\n                int worldZ = minZ + z;\n                surfaceContext.updateXZ(worldX, worldZ);\n                int stoneDepthAbove = 0;\n                int waterHeight = Integer.MIN_VALUE;\n                int stoneDepthOffset = Integer.MAX_VALUE;\n\n                for (int y = minY; y >= maxY; y -= 1) {\n                    var worldPos = new Vec(worldX, y, worldZ);\n                    var oldState = chunk.getBlock(worldPos);\n                    if (oldState.compare(Block.AIR)) {\n                        stoneDepthAbove = 0;\n                        waterHeight = Integer.MIN_VALUE;\n                        continue;\n                    }\n                    if (oldState.registry().isLiquid()) {\n                        if (waterHeight == Integer.MIN_VALUE) {\n                            waterHeight = y + 1;\n                        }\n                        continue;\n                    }\n                    if (stoneDepthOffset >= y) {\n                        stoneDepthOffset = Integer.MIN_VALUE;\n                        for (int i = y - 1; i >= minY; i -= 1) {\n                            Block state = chunk.getBlock(new Vec(worldX, i, worldZ));\n                            if (state.compare(Block.AIR) || state.registry().isLiquid()) {\n                                stoneDepthOffset = i + 1;\n                                break;\n                            }\n                        }\n                    }\n                    stoneDepthAbove += 1;\n                    int stoneDepthBelow = y - stoneDepthOffset + 1;\n\n                    if (!oldState.equals(this.defaultBlock)) {\n                        continue;\n                    }\n                    surfaceContext.updateY(stoneDepthAbove, stoneDepthBelow, waterHeight, y);\n                    var newState = ruleWithContext.apply(worldX, y, worldZ);\n                    if (newState != null) {\n                        chunk.setBlock(worldPos, newState);\n                    }\n                }\n            }\n        }\n    }\n\n    public int getSurfaceDepth(double x, double z) {\n        double noise = this.surfaceNoise.sample(x, 0, z);\n        double offset = this.random.at((int) x, 0, (int) z).nextDouble() * 0.25;\n        return (int) (noise * 2.75 + 3 + offset);\n    }\n\n    public double getSurfaceSecondary(double x, double z) {\n        return this.surfaceSecondaryNoise.sample(x, 0, z);\n    }\n\n    public WorldgenRandom getRandom(String name) {\n        return positionalRandoms.computeIfAbsent(name, this.random::fromHashOf);\n    }\n}"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/VanillaTestGenerator.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport de.articdive.jnoise.generators.noisegen.opensimplex.FastSimplexNoiseGenerator;\nimport de.articdive.jnoise.generators.noisegen.white.WhiteNoiseGenerator;\nimport de.articdive.jnoise.pipeline.JNoise;\nimport net.minestom.server.coordinate.Point;\nimport net.minestom.server.coordinate.Vec;\nimport net.minestom.server.instance.block.Block;\nimport net.minestom.server.instance.generator.GenerationUnit;\nimport net.minestom.server.instance.generator.Generator;\nimport net.minestom.server.world.biome.Biome;\nimport org.jetbrains.annotations.NotNull;\n\npublic class VanillaTestGenerator implements Generator {\n\n    private final JNoise noise = JNoise.newBuilder()\n            .fastSimplex(FastSimplexNoiseGenerator.newBuilder()\n                    .build())\n            .scale(1.0 / 32.0)\n            .build();\n    private final JNoise treeNoise = JNoise.newBuilder()\n            .white(WhiteNoiseGenerator.newBuilder().build())\n            .build();\n\n    private synchronized double noise(JNoise noise, double x, double z) {\n        return noise.evaluateNoise(x, 0, z);\n    }\n\n    @Override\n    public void generate(@NotNull GenerationUnit unit) {\n        var modifier = unit.modifier();\n        modifier.fillBiome(Biome.PLAINS);\n\n        Point start = unit.absoluteStart();\n        Point end = unit.absoluteEnd();\n\n        for (int x = start.blockX(); x < end.blockX(); x++) {\n            for (int z = start.blockZ(); z < end.blockZ(); z++) {\n\n                double heightDelta = noise(noise, x, z);\n                int height = (int) (64 - heightDelta * 16);\n                int bottom = 0;\n                int stone = height;\n                int dirt = stone + 5;\n                int grass = dirt + 1;\n\n                for (int y = bottom; y < stone; y++) {\n                    modifier.setBlock(x, y, z, Block.STONE);\n                }\n\n                for (int y = stone; y < dirt; y++) {\n                    modifier.setBlock(x, y, z, Block.DIRT);\n                }\n\n                for (int y = dirt; y < grass; y++) {\n                    modifier.setBlock(x, y, z, Block.GRASS_BLOCK);\n                }\n\n                if (height < 64) {\n                    // Too low for a tree\n                    // However we can put water here\n                    for (int y = height; y < 64; y++) {\n                        modifier.setBlock(x, y, z, Block.WATER);\n                    }\n                    continue;\n                }\n\n                if (noise(treeNoise, x, z) > 0.9) {\n                    Point treePos = new Vec(x, grass, z);\n                    unit.fork(setter -> spawnTree(setter, treePos));\n                }\n            }\n        }\n    }\n\n    private void spawnTree(Block.Setter setter, Point pos) {\n        int trunkX = pos.blockX();\n        int trunkBottomY = pos.blockY();\n        int trunkZ = pos.blockZ();\n\n        for (int i = 0; i < 2; i++) {\n            setter.setBlock(trunkX + 1, trunkBottomY + 3 + i, trunkZ, Block.OAK_LEAVES);\n            setter.setBlock(trunkX - 1, trunkBottomY + 3 + i, trunkZ, Block.OAK_LEAVES);\n            setter.setBlock(trunkX, trunkBottomY + 3 + i, trunkZ + 1, Block.OAK_LEAVES);\n            setter.setBlock(trunkX, trunkBottomY + 3 + i, trunkZ - 1, Block.OAK_LEAVES);\n\n            for (int x = -1; x <= 1; x++) {\n                for (int z = -1; z <= 1; z++) {\n                    setter.setBlock(trunkX + x, trunkBottomY + 2 + i, trunkZ - z, Block.OAK_LEAVES);\n                }\n            }\n        }\n\n        setter.setBlock(trunkX, trunkBottomY, trunkZ, Block.OAK_LOG);\n        setter.setBlock(trunkX, trunkBottomY + 1, trunkZ, Block.OAK_LOG);\n        setter.setBlock(trunkX, trunkBottomY + 2, trunkZ, Block.OAK_LOG);\n        setter.setBlock(trunkX, trunkBottomY + 3, trunkZ, Block.OAK_LOG);\n        setter.setBlock(trunkX, trunkBottomY + 4, trunkZ, Block.OAK_LEAVES);\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/VanillaWorldGenerationFeature.java",
    "content": "package net.minestom.vanilla.generation;\n\nimport net.kyori.adventure.key.Key;\nimport net.minestom.vanilla.VanillaReimplementation;\nimport net.minestom.vanilla.datapack.Datapack;\nimport net.minestom.vanilla.datapack.DatapackLoadingFeature;\nimport net.minestom.vanilla.datapack.worldgen.NoiseSettings;\nimport net.minestom.vanilla.instance.SetupVanillaInstanceEvent;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Set;\n\npublic class VanillaWorldGenerationFeature implements VanillaReimplementation.Feature {\n\n    @Override\n    public void hook(@NotNull HookContext context) {\n        context.vri().process().eventHandler().addListener(SetupVanillaInstanceEvent.class, event -> {\n\n            Key plains = Key.key(\"minecraft:plains\");\n            DatapackLoadingFeature datapackLoading = context.vri().feature(DatapackLoadingFeature.class);\n            Datapack datapack = datapackLoading.current();\n\n            Datapack.NamespacedData data = datapack.namespacedData().get(\"minecraft\");\n            if (data == null) {\n                throw new IllegalStateException(\"minecraft namespace not found\");\n            }\n\n            NoiseSettings settings = data.world_gen().noise_settings().file(\"overworld.json\");\n//            BiomeSource.fromJson()\n\n//            ThreadLocal<NoiseChunkGenerator> generators = ThreadLocal.withInitial(() -> new NoiseChunkGenerator(datapack, (x, y, z, sampler) -> plains, settings, event.getInstance().getDimensionType()));\n//            event.getInstance().setChunkGenerator(new ChunkGenerator() {\n//                @Override\n//                public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {\n//                    generators.get().generateChunkData(batch, chunkX, chunkZ);\n//                }\n//\n//                @Override\n//                public @Nullable List<ChunkPopulator> getPopulators() {\n//                    return null;\n//                }\n//            });\n        });\n    }\n\n    @Override\n    public @NotNull Key key() {\n        return Key.key(\"vri:worldgeneration\");\n    }\n\n    @Override\n    public @NotNull Set<Class<? extends VanillaReimplementation.Feature>> dependencies() {\n        return Set.of(DatapackLoadingFeature.class);\n    }\n}\n"
  },
  {
    "path": "world-generation/src/main/java/net/minestom/vanilla/generation/VanillaWorldgen.java",
    "content": "package net.minestom.vanilla.generation;\n\npublic final class VanillaWorldgen {\n    // TODO: Vanila worldgen\n\n}\n"
  }
]